Session是一种服务器端技术,Session对象在服务器端创建,通常采用散列表来存储信息,例如,Tomcat的Session实现采用HashMap对象来存储属性名和属性值。
Cookie是由Netscape公司发明的、用于跟踪用户会话的一种方式。Cookie是由服务器发送给客户的片段信息,存储在客户端浏览器的内存中或硬盘上,在客户随后对该服务器的请求中发回它。
Session与Cookie的最大的区别是,Session在服务端保存信息,Cookie在客户端保存信息。为了跟踪用户的会话,服务器端在创建Session后,需要将Session ID交给客户端,在客户端下次请求时,将这个ID随请求一起发送回来。可以采用Cookie或URL重写的方式,将Session ID发送给客户端。
在5.2.2节的基于Cookie的会话跟踪的例子中,当访问了Servlet后,关闭浏览器,再次打开浏览
器访问
Servlet
时,可以看到开始了一次新的会话。如果我们同时打开两个浏览器,访问同一个
URL
,那么每一个浏览器进程都将开始一个新的会话,如图
5-8
所示。而在“使用
Cookie
的实例”中,当我们
登录后,关闭浏览器,再打开浏览器,访问
GreetServlet2
时,直接出现了欢迎页面。然后我们同时打开两个浏览器,访问
GreetServlet2
时,也直接出现了欢迎页面。也就说,在
5.2.2
节的例子中,保存Session ID
的Cookie
在关闭浏览器后就删除了,不能在多个浏览器进程间共享。而在“使用Cookie
的实例”的例子中,保存用户名和密码的Cookie
在浏览器关闭后,再次打开,仍然存在,可以在多个浏览器进程间共享。
通常,我们将用于会话跟踪的Cookie叫做会话Cookie,
在
Servlet
规范中,用于会话跟踪的Cookie
的名字必须是JSESSIONID
,它通常保存在浏览器的内存中。在浏览器内存中的会话Cookie不能被不同的浏览器进程所共享。
在网络上,很多人有一种错误的认识,认为以不同的方式打开浏览器窗口,或者使用其他的非
IE
浏览器就可以在不同的浏览器进程之间共享会话Cookie
。下面,我们和读者一起看看这种错误认识是如何产生的。
首先看看IE浏览器,启动Tomcat服务器,打开IE浏览器,访问5.2.2节中的例子程序,在地址栏中输入:http://localhost:8080/ch05/login,然后单击IE浏览器菜单栏上的“文件”→“新建”→“窗口”(也可以在浏览器窗口上按下Ctrl+N组合键)打开一个新窗口,分别刷新原窗口和新窗口,可以看到两个窗口中显示的Session ID是同一个。由实验可以看到,某些方式下打开的多个IE浏览器,可以共享内存中的Cookie,而另外一些方式打开的多个IE浏览器,则不能共享内存中的Cookie。再使
用其他浏览器进行验证,发现有的浏览器(例如
Mozilla FireFox
)不管以什么方式打开窗口,都可以共享内存中的
Cookie
。由此,一个错误的结论就产生了:不同的浏览器对内存中的
Cookie
有不同的处理方式,有的浏览器(FireFox)可以在多个浏览器进程间共享会话Cookie,IE浏览器是否共享Cookie,要看浏览器打开的方式。
笔者为什么会说上述结论是错误的呢?这是因为得出结论的人不了解进程的概念。读者在以上述方式打开两个IE浏览器窗口后(如果有其他的IE浏览器打开,请先关闭),同时按下Ctrl+Alt+Del组合键,选择“任务管理器”,切换到“进程”标签页,在“映像名称”下查找IEXPLORE.EXE,看看找到了几个IEXPLORE.EXE,是不是只有一个!那么这说明了什么?说明我们所看到的两个IE浏览器窗口实际上是属于同一个IE浏览器进程,难怪它们能共享内存中的会话Cookie。以其他方式打开一个IE浏览器,可以看到在“映像名称”下多了一个IEXPLORE.EXE进程,既然是两个IE浏览器进程的窗口,当然无法共享内存中的Cookie了。同样的道理,Mozilla FireFox不管以什么方式打开窗口,始终只启动了一个进程,既然这些窗口同属于一个进程,当然可以共享内存中的Cookie了。所以我们说,对于存储在内存中的Cookie,是不能被不同的浏览器进程所共享的。共享只能发生在同一个浏览器进程的不同窗口中(因为这些窗口共享同一个进程地址空间)。关于进程和线程的知识,读者可以参看相关的书籍。
对于保存在硬盘上的Cookie,因为是在外部的存储设备中存储,所以可以在多个浏览器进程间共享。
有些人对Session的使用存在着一种误解,认为浏览器一旦关闭,Session就消失了。这是因为有的人看到,关闭浏览器后,再打开一个浏览器,就开始了一次新的会话,从而得出了结论。再回头看看第5.1节中的顾客在超市存包的例子,顾客存好包,购物完毕,忘了取包就走了,但存包处的管理员不知道顾客已经走了,所以他必须继续用柜子(相当于Session)存放顾客的物品,直到长时间没有人来取(Session的超时值发生),管理员才清除柜子。之所以会有“浏览器一旦关闭,Session就消失了”这种错误的认识,主要是因为保存Session ID的Cookie是存储在浏览器的内存中,一旦浏览器关闭,Cookie将被删除,Session ID也就丢失了。当再次打开浏览器连接服务器时,服务器没有收到Session ID,当然也就无法找到先前的Session,于是服务器就创建了一个新的Session。而这个时候先前的Session是仍然存在的,直到设置的Session超时时间间隔发生,Session才被服务器清除。如果我们将会话Cookie保存到硬盘上,或者通过某种技术手段改写浏览器向服务器发送的请求报头,将原先的Session ID发送给服务器,则再次打开的浏览器就能够找到原来的Session了。
我们想了解一下,用于会话跟踪的Cookie是如何创建的,为何只能保存在浏览器的内存中,而不能保存到用户的硬盘上。在Tomcat 6.0.16.中,Session的创建是调用org.apache.catalina.connector.Request类中的doGetSession()方法来完成的。下面我们给出这个方法的代码片段:
Java代码
1.protected Session doGetSession(boolean create) 2.{ 3. … 4. // Creating a new session cookie based on that session 5. if ((session != null) && (getContext() != null) 6. && getContext().getCookies()) 7. { 8. Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME, 9. session.getIdInternal()); 10. configureSessionCookie(cookie); 11. response.addCookieInternal(cookie); 12. } 13. 14. if (session != null) 15. { 16. session.access(); 17. return (session); 18. } 19. else 20. { 21. return (null); 22. } 23.} 24. 25.protected void configureSessionCookie(Cookie cookie) 26.{ 27. cookie.setMaxAge(-1); 28. String contextPath = null; 29. if (!connector.getEmptySessionPath() && (getContext() != null)) 30. { 31. contextPath = getContext().getEncodedPath(); 32. } 33. if ((contextPath != null) && (contextPath.length() > 0)) 34. { 35. cookie.setPath(contextPath); 36. } 37. else 38. { 39. cookie.setPath("/"); 40. } 41. if (isSecure()) 42. { 43. cookie.setSecure(true); 44. } 45.}
代码的第8行,我们看到非常熟悉的创建Cookie对象的代码,Cookie的名字是Globals.SESSION_ COOKIE_NAME,SESSION_COOKIE_NAME被定义为静态的常量,其值为JSESSIONID。Cookie的值是调用session.getIdInternal ()得到的Session ID。第10行,调用了configureSessionCookie()方法来配置会话Cookie。我们转到configureSessionCookie()方法中,第27行,调用Cookie对象的setMaxAge()方法设置Cookie的生存时间,在“使用Cookie的实例”的例子中,我们说过,
如果时间值为负数,那么当客户端的浏览器退出,Cookie将会被删除。看到这儿,我们就知道了为什么会话Cookie只能保存在内存中了,这是由Tomcat的实现决定的。第35行,调用Cookie对象的setPath()方法,指定这个Cookie在当前Web应用程序的上下文路径下有效。