Session机制详解(转)
转自:http://tech.it168.com/j/2006-07-19/200607191220593.shtml
由于HTTP协议是无状态的,而出于种种考虑也不希望使之成为有状态的,因此,后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。
1.cookie
cookie的内容主要包括:名字,值,过期时间,路径和域。
其中域可以指定某一个域比如.google.com,路径就是跟在域名后面的URL路径,比如/或者/foo等等,路径与域合在一起就构成了cookie的作用范围。如果不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。
2.session
session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。
当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。
保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。
由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面,附加方式也有两种,一种是作为URL路径的附加信息,另一种是作为查询字符串附加在URL后面。
另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。
在谈论session机制的时候,常常听到这样一种误解“只要关闭浏览器,session就消失了”。其实是除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session。
恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。
javax.servlet.http.HttpSession
HttpSession是Java平台对session机制的实现规范。
首先,Web服务器Server提供了一系列的参数来控制它的HttpSession的实现,包括使用cookie的开关选项,使用URL重写的开关选项,session持久化的设置,session失效时间的设置,以及针对cookie的各种设置,比如设置cookie的名字、路径、域,cookie的生存时间等。
一般情况下,session都是存储在内存里,当服务器进程被停止或者重启的时候,内存里的session也会被清空,如果设置了session的持久化特性,服务器就会把session保存到硬盘上,当服务器进程重新启动或这些信息将能够被再次使用,Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。
session的隔离
从Tomcat设置的cookie路径来看,它对不同的应用程序设置的cookie路径是不同的,这样不同的应用程序所用的session id是不同的,因此即使在同一个浏览器窗口里访问不同的应用程序,发送给服务器的session id也可以是不同的。
根据这个特性,我们可以推测Tomcat中session的内存结构大致如下。
3.常见问题
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。
7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session
对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插件造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。
出现这一问题的大部分原因都是程序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。
3.Tomcat Session的管理机制
ApplicationContext:Servlet规范中ServletContext的实现
StandardContext:Tomcat定义的Context默认实现.维护了一份SessionManager对象,管理Session对象.所有的Session对象都存放在Manager定义的Map<String,Session>容器中.
StanardManager:标准的Session管理,将Session存放在内容,Web容器关闭的时候,持久化到本地文件
PersistentManager:持久化实现的Session管理,默认有两种实现方式:
--持久化到本地文件
--持久化到数据库
Session创建过程:
1 protected Session doGetSession(boolean create) { 2 3 // There cannot be a session if no context has been assigned yet 4 if (context == null) 5 return (null); 6 7 // Return the current session if it exists and is valid 8 if ((session != null) && !session.isValid()) 9 session = null; 10 if (session != null) 11 return (session); 12 13 // Return the requested session if it exists and is valid 14 Manager manager = null; 15 if (context != null) 16 manager = context.getManager(); 17 if (manager == null) 18 return (null); // Sessions are not supported 19 if (requestedSessionId != null) { 20 try { 21 session = manager.findSession(requestedSessionId); 22 } catch (IOException e) { 23 session = null; 24 } 25 if ((session != null) && !session.isValid()) 26 session = null; 27 if (session != null) { 28 session.access(); 29 return (session); 30 } 31 } 32 33 // Create a new session if requested and the response is not committed 34 if (!create) 35 return (null); 36 if ((context != null) && (response != null) && 37 context.getCookies() && 38 response.getResponse().isCommitted()) { 39 throw new IllegalStateException 40 (sm.getString("coyoteRequest.sessionCreateCommitted")); 41 } 42 43 // Attempt to reuse session id if one was submitted in a cookie 44 // Do not reuse the session id if it is from a URL, to prevent possible 45 // phishing attacks 46 if (connector.getEmptySessionPath() 47 && isRequestedSessionIdFromCookie()) { 48 session = manager.createSession(getRequestedSessionId()); 49 } else { 50 session = manager.createSession(null); 51 } 52 53 // Creating a new session cookie based on that session 54 if ((session != null) && (getContext() != null) 55 && getContext().getCookies()) { 56 Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME, 57 session.getIdInternal()); 58 configureSessionCookie(cookie); 59 response.addCookieInternal(cookie, context.getUseHttpOnly()); 60 } 61 62 if (session != null) { 63 session.access(); 64 return (session); 65 } else { 66 return (null); 67 } 68 69 }
在org.apache.catalina.core.ContainerBase中,会启动一个后台线程,跑一些后台任务,Session过期任务是其中之一。
Session的过期策略:
1 //过期 2 public void processExpires() { 3 4 long timeNow = System.currentTimeMillis(); 5 Session sessions[] = findSessions(); 6 int expireHere = 0 ; 7 8 if(log.isDebugEnabled()) 9 log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length); 10 for (int i = 0; i < sessions.length; i++) { 11 if (sessions[i]!=null && !sessions[i].isValid()) { 12 expireHere++; 13 } 14 } 15 long timeEnd = System.currentTimeMillis(); 16 if(log.isDebugEnabled()) 17 log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere); 18 processingTime += ( timeEnd - timeNow ); 19 20 }
Session钝化机制
Session的主要数据被存储在服务器内存中,而服务器会为每个在线用户创建一个Session对象,当在线用户很多时,例如同时有几万或是几十万在线的情况下,Session内存的开销将会十分巨大,会影响Web服务器性能。而Session的钝化机制刚好可解决此问题。
Session钝化机制的本质就在于把服务器中不经常使用的Session对象暂时序列化到系统文件系统或是数据库系统中,当被使用时反序列化到内存中,整个过程由服务器自动完成。