用户的活动发生在多个请求和相应之中,作为Web服务器来说,必须能够采用一种机制来惟一地标识一个用户,同时记录该用户的状态,这就是典型的Web需求里面的会话跟踪机制。
Java Servlet API利用Session来跟踪会话。利用Session,服务器可以把一个客户的所有请求联系在一起,并记住客户的操作状态。当用户第一次连接到服务器的时候,服务器为其建立一个Session,并分配给用户一个惟一的标识Session ID,以后用户每次提交请求的时候,都要将标识一起提交。服务根据标识找出特定的Session,用这个Session记录用户的状态。
这个过程就好像我们去超市购物存包的过程:一个顾客(相当于客户端)到超市购物,如果顾客需要存包的话(相当于请求Session对象),那么他可以到存包处(相当于Web服务器)去存,管理员将顾客的包放到一个柜子里(相当于建立了一个Session),然后将一个号码牌给顾客(相当于为顾客分配了一个惟一的SessionID)。当顾客下一次到存包处的时候,需要将号码牌交给管理员,管理员会根据号码牌找到相应的柜子,根据顾客的请求(HttpRequest)取出、添加、更换物品,然后将号码牌再次交给顾客。顾客每次到存包处的时候都要提供号码牌;顾客每次到存包处的时候,都要提供号码牌,存包处的管理员对顾客的每次请求都要做出响应(HttpRequest)。当然了,如果顾客不需要存包的话,上述过程也就不用发生(相当于Web服务器不用为用户建立Session对象)。
从上面的过程我们可以看出,通过在每一个请求和响应中包含Session ID,服务器就可以将一个用户和另外一个用户区别开来。在Servlet规范中,支持下列常见的三种机制:
- Cookies
- URL重写
- 表单隐藏字段
下面我们分别介绍这三种机制,在这之前,我们需要了解Web服务器端是如何创建SessionID的。
当用户提交一个新的请求的时候(这里讨论的是新请求,旧请求在下面讨论机制的时候再讨论),服务器端会与其建立一个会话连接,但注意,这只是一个物理意义上的连接,此时服务器端会根据需要创建Session对象(包括SessionID和SessionInfo)。所谓“根据需要”是指,如果用户请求的页面中(如Servlet,JSP等)包含对Session对象的请求(如getSession()等),那么服务器端就会为其创建一个Session对象。而如果用户的请求页面之中始终没有对Session对象的请求,那么服务器端将不会为之创建Session对象。(JSP文件在服务器端被编译成.java的时候,服务器会预先为其创建Session内建对象;但是如果是Servlet文件的话,需要我们自己写程序主动调用Session对象)。我们可以用一个非常简单的例子程序来说明上述观点。
Servlet的文件代码如下:
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- HttpSession session = request.getSession(); // 此句根据需要注释掉
- }
建立一个空的JSP的文件。
再建立一个session监听器,在web.xml里面配置好,监听器的文件代码如下:
- public class MySessionListener implements HttpSessionListener{
- public void sessionCreated(HttpSessionEvent se){
- System.out.println("session创建成功");
- }
- public void sessionDestroyed(HttpSessionEvent se){
- System.out.println("session被销毁");
- }
- }
编译,运行。打开IE,当我们请求Servlet页面的时候,会触发监听器,console端会输出"session创建成功"字符;
打开一个新的IE,当我们请求JSP页面的时候,同样会触发监听器,console端会输出"session创建成功"字符;
此时,注释掉Servlet文件中对Session对象调用的代码,重新编译运行,再打开一个新的IE,此时我们发现,监听器并没有被触发,说明Session对象并没有被创建。
再来看看创建后的SessionID是如何进行会话跟踪的。
1、Cookies机制
(1) Cookies的定义
Cookies是一种能够让网站服务器把少量数据储存到客户端的硬盘或内存,或是从客户端的硬盘读取数据的一种技术。Cookies是当你浏览某网站时,由Web服务器置于你硬盘上的一个非常小的文本文件,它可以记录你的用户ID、密码、浏览过的网页、停留的时间等信息。当你再次来到该网站时,网站通过读取Cookies,得知你的相关信息,就可以做出相应的动作,如在页面显示欢迎你的标语,或者让你不用输入ID、密码就直接登录等等。从本质上讲,它可以看作是你的身份证。但Cookies不能作为代码执行,也不会传送病毒,且为你所专有,并只能由提供它的服务器来读取。保存的信息片断以"名/值"对(name-value pairs)的形式储存,一个"名/值"对仅仅是一条命名的数据。一个网站只能取得它放在你的电脑中的信息,它无法从其它的Cookies文件中取得信息,也无法得到你的电脑上的其它任何东西。 Cookies中的内容大多数经过了加密处理,因此一般用户看来只是一些毫无意义的字母数字组合,只有服务器的CGI处理程序才知道它们真正的含义。
Cookies可以有不同的保存时间,一个是Session Cookies,保存在内存中,另一个是Persistent Cookies,保存在用户硬盘上,可以通过调用Cookies.setAge()来设置Cookies的保存类型。如果将时间值设为负数,那么当客户端的浏览器关闭的时候,Cookies将会被删除,这是一个Session Cookies;如果设置为0,则cookie不会被保存,立刻删除无效;如果设置的时间值为正数,Cookies就会被保存到客户机器的硬盘上,并保存相应长的时间,这是一个persisitant Cookies。
(2) 服务器端设置Cookies中JSESSIONID
我们这里以Tomcat 5为例,说明Session Cookies是如何从服务器端发送到客户端的。
在Tomcat服务器端,Session的创建是通过调用org.apache.catalina.connector.Request类中的doGetSession()方法来完成的。下面我们给出这个方法的代码片段:
- protected Session doGetSession(boolean create){
- ...
- // creating a new session cookie based on that session
- if(session!=null && getContext()!=null && getContext().getCookies()){
- Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME,session.getId());
- configureSessionCookie(cookie);
- response.addCookie(cookie);
- }
- ...
- }
- protected void configureSessionCookie(Cookie cookie){
- cookie.setMaxAge(-1);
- String contextPath=null;
- if((!connector.getEmptySessionPath())&&(getContext()!=null)){
- contextPath = getContext().getEncodedPath();
- }
- if((contextPath!=null)&&(contextPath.length()>0)){
- cookie.setPath(contextPath);
- }else{
- cookie.setPath("/");
- }
- if(isSecure()){
- cookie.setSecure(true);
- }
- }
从以上代码可以看出,服务器端在创建session的时候,会将sessionID当做一个Cookie写入response中,由于设置了cookie.setMaxAge(-1),这个cookie是保存在内存中,浏览器关闭时会被销毁。这样我们每次打开两个不同的浏览器的时候,得到的session值是不一样的,而对于同一个浏览器的两个不同应用,由于共用内存空间(从任务管理器中只能看到一个"explore.exe"进程),所以访问的是同一个session。如果需要在不同的浏览器应用之间共享数据,需要设置cookie的生存周期为正数。
(3) 服务器端读取Cookies中的JSESSIONID
当用户向服务器端发送请求时,如果请求中包含对session对象的请求,服务器就会从request的cookie中取得sessionID,然后查看是否已经存在这个sessionID,如果已经存在,则通过该SessionID调用相应的的session信息,如果不存在,则为该请求重新分配一个sessonID。
2、URL重写机制
由于cookie可以被人为的禁止,必须有其它的机制以便在cookie被禁止时仍然能够把session id传递回服务器,经常采用的一种技术叫做 URL重写,就是把session id附加在URL路径的后面,附加的方式也有两种,一种是作为URL路径的附加信息,另一种是作为查询字符串附加在 URL后面。网络在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。
对所有的URL使用URL重写,包括超链接,form的action,和重定向的URL。每个引用你的站点的URL,以及那些返回给用户的URL(即使通过间接手段,比如服务器重定向中的Location字段)都要添加额外的信息。这意味着在你的站点上不能有任何静态的HTML页面(至少静态页面中不能有任何链接到站点动态页面的链接)。因此,每个页面都必须使用servlet 或JSP动态生成。即使所有的页面都动态生成,如果用户离开了会话并通过书签或链接再次回来,会话的信息都会丢失,因为存储下来的链接含有错误的标识信息-该URL后面的SESSION ID已经过期了。
读者也许会考虑,在开发Web应用程序的时候,如何去判断客户端是否禁用了Cookie,从而决定是否采用URL重写机制去跟踪用户的会话。实际上,客户端是否禁用了Cookie,不需要我们去判断,Servlet容易会帮我们做这件事情。我们在开发Web程序的时候,只要对所有的链接和重定向语句中的所有URL都调用encodeURL()和encodeRedirectURL()方法进行编码就行了。这两个方法在执行时,首先判断当前的Servlet是否已经执行了HttpSession的invalidate()方法,如果已经执行了,直接返回参数URL。接下来,判断客户端是否已经禁用了Cookie,如果没有禁用,则直接返回参数URL,如果禁用了Cookie,则在参数URL中附加SessionID,返回编码后的URL。
了解了encodeURL()和encodeRedirectURL()方法的工作原理以后,就可以结合基于cookie和URL重写的机制来跟踪用户对话。如果一个Web应用程序的功能实现依赖于某个用户会话的跟踪,那么你可以将所有的页面实现为动态的,并在代码中使用URL重写机制。在运行时,Servlet容易就会自动根据客户端的情况来选择会话跟踪机制。
3、表单隐藏字段
就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。比如下面的表单:
- <form name="testform" action="/xxx">
- <input type="text">
- </form>
在被传递给客户端之前将被改写成:
- <form name="testform" action="/xxx">
- <input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
- <input type="text">
- </form>
好了,三种机制就讨论到这儿,我们再来说说Session的生命周期。每个Session对象都会有特定的生命周期。Session的超时时间间隔可以通过HttpSession接口的setMaxInactiveInterval()方法设置,也可以利用Web服务器的配置文件(web.xml)来设定:
- <session-config>
- <session-timeout>5</session-timeout>
- </session-config>
Tomcat服务器的session超时间隔在%CATALINA_HOME%/conf/web.xml文件中有默认设置,默认为30分钟。
session在下列情况下被删除:
A.程序调用HttpSession.invalidate()
B.距离上一次收到客户端发送的session id时间间隔超过了session的最大有效时间
C.服务器进程被停止