本文讨论的语境是java EE servlet。
我们都知道session的实现主要两种方式:cookie与url重写,而cookie是首选(默认)的方式,因为各种现代浏览器都默认开通cookie功能,但是每种浏览器也都有允许cookie失效的设置。
由于浏览器默认启动cookie功能,而且普通客户一般都不会取消cookie功能。久而久之,我们写代码的时候,也就不会在意session的具体实现,其实这里面还是有很多值得注意的地方,尤其在用户取消cookie功能的情况下。
一 servlet session实现与接口简要介绍:
servlet规范规定实现session的cookie名称强制为jsessionid(在servlet 3.0 可以自定义了),在浏览器第一次请求的时候,服务器产生一个唯一的id,并把这个id设置给一个名为jsessionid的cookie,然后再通过reponse的addcookie,输出到浏览器端。其实这些东西我们都可以通过debug模式下的去查看服务器,来验证这些内容;或者用http协议拦截器来查看,因为servlet的所有接口也都是基于http协议的,
下面基于http协议解释一下:

浏览器第一次请求:
GET /cxt/index.do HTTP/1.1
...
由于是第一次请求,所以没有cookie要推给服务器

服务器返回:
HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=3EF0AEC40F2C6C30A0580844C0E6B2E8; Path=/cxt
...
<form name="form" method="post" action="/sas/Login.do;jsessionid=3EF0AEC40F2C6C30A0580844C0E6B2E8">
...
</form>
...

由于服务器没发现浏览器没提供任何cookie,服务器不知道是浏览器未提供cookie的原因:可能是cookie功能取消了,也可能是第一次访问。所以服务器生成一个名为jsessionid的cookie,用Set-Cookie来把cookie推给浏览器;并且,服务器的servlet在生成html页面的时候需要用reponse.encodeURL方法来编码url,该方法其实就是用来实现url重写功能的,这是因为浏览器可能是因为取消cookie功能,而未提供cookie的。服务器为了确保下次提交成功,必须确保生成给浏览器端的url带有jsessionid。

若cookie功能没取消,则浏览器浏览器第二次请求:
POST /cxt/login.do;jsessionid=3EF0AEC40F2C6C30A0580844C0E6B2E8 HTTP/1.1
Cookie: JSESSIONID=3EF0AEC40F2C6C30A0580844C0E6B2E8;
...
浏览器的下一次请求的时候用http的Cookie属性把当前domain的Cookie都推给服务器,来表明自己的身份。这次,服务器知道浏览器支持cookie功能,servlet不需要再使用reponse.encodeURL来编码url了

若浏览器cookie功能取消,则浏览器请求内容为
POST /cxt/login.do?jsessionid=3EF0AEC40F2C6C30A0580844C0E6B2E8 HTTP/1.1
...

服务器在接受到上述内容是,通过url后面的jsessionid参数知道这个请求与上一次请求是同一个session

与session有关的类接口:
HttpServletRequest.getSession
HttpSession
HttpServletResponse.encodeURL

二 实现url重写的编码注意点
1.既然浏览器可能被取消cookie功能,那么我们输出给客户端的代码中必须要支持url重写功能,分几种情况解释
假如纯粹用jsp/servlet来写,那么我们必须手动用reponse.encodeURL来编码每一个url,包括<a>的href,form的action,或者其他href
2.使用其它web框架的话,最好消息使用框架提供的输出href功能,否则会有匪夷所思的结果。
比如用struts,若struts单独提供了一个tag来实现html:rewrite,比如<html:rewrite action='logout'/>,在没有jsessionid cookie的情况下,最后会生成http://localhost:8080/ctx/logout.do;jsessionid=0916FB057C169069;若有jsessionid cookie,则会生成http://localhost:8080/ctx/logout.do。但是我们还注意到html:rewrite还有一个href属性,假如我们用href属性的话,则struts不会生成带有jsessionid参数。
struts中提供url重写的功能的还有html:link与html:form,比如<html:link action="logout">logout</html:link>,这个tag功能与html:rewrite相似,也有href属性,生成带有<a>标签的html内容。<html:form action="/Login"></html:form>,html:form没有href属性,只有action属性。
3.假若没有使用任何框架,则可以使用jstl提供的url重写功能
jstl提供了标签来实现c:url,比如<c:url value="logout.do" />,这个也会根据浏览器是否支持cookie,来生成带有jsessionid属性的url。

相信通过上面的总结,是我们对怎么使用session与cookie有更深入的认识。记住,在用jsp/servlet实现系统的时候,尽量不要自己写<a>标签,最好使用系统框架自带的标签来实现,否则浏览器取消cookie功能的话,系统不支持url重写功能。

使用表单隐藏域跟踪Session 重写URL跟踪Session

Servlet容器先在客户端浏览器中保存一个Sessin ID,以后在浏览器发出的HTTP请求中就会包含这个Session ID,Servlet容器读取HTTP请求中的Session ID,就能判断出来自各个浏览器进程的HTTP请求属于哪个会话。这一过程称为Session跟踪。

  • 如果浏览器支持Cookie,Servlet容器就把Session ID作为Cookie保存在浏览器中。

  • 如果浏览器出于安全的原因,禁用Cookie,不允许服务器像客户端存放Cookie。Servlet容器可以重写Web组件的URL,把Session ID添加到URL信息中。

HttpServletResponse接口提供了重写URL的方法

 public String encodeURL(String url)  

 public String encodeRedirectURL(String url)  

只有在当前Web组件支持Session,并且浏览器不支持Cookie的情况下,encodeURL方法才会重写URL,否则直接返回参数指定的原始URL。

例如:

encodeurl.jsp中包含如下代码:

<a href="<%%= response.encodeURL("TEST.JSP")>/>  

当浏览器请求访问 encodeurl.jsp 文件时,如果当前JSP页面支持Session,并且浏览器不支持Cookie,则上面的链接被解析为如下形式:

<a href="TEST.JSP;jsessionid=9549349941565F01191DB6F290F68EF" />   


TOMCAT判断客户端浏览器是否支持Cookie的依据是请求中是否含有Cookie。尽管客户端可能会支持Cookie,但是由于第一次请求时不会携带任何Cookie(因为并无任何Cookie可以携带),URL地址重写后的地址中仍然会带有jsessionid。当第二次访问时服务器已经在浏览器中写入Cookie了,因此URL地址重写后的地址中就不会带有jsessionid了。

在Session中禁用Cookie

既然可能客户浏览器不支持Cookie,索性禁止Session使用Cookie,统一使用URL地址重写会更好一些。

Java Web规范支持通过配置的方式禁用Cookie

下面举例说一下怎样通过配置禁止使用Cookie。

打开项目sessionWeb的WebRoot目录下的META-INF文件夹(跟WEB-INF文件夹同级,如果没有则创建),打开context.xml(如果没有则创建),编辑内容如下:

/META-INF/context.xml  

<?xml version='1.0' encoding='UTF-8'?>  

<Context path="/sessionWeb"cookies="false">  

</Context>  

或者修改Tomcat全局的conf/context.xml,修改内容如下:

//context.xml  

<!-- The contents of this file will be loaded for eachweb application -->  

<Context cookies="false">  

    <!-- ... 中间代码略 -->  

</Context>  

部署后TOMCAT便不会自动生成名JSESSIONID的Cookie,Session也不会以Cookie为识别标志,而仅仅以重写后的URL地址为识别标志了。
注意:该配置只是禁止Session使用Cookie作为识别标志,并不能阻止其他的Cookie读写。也就是说服务器不会自动维护名为JSESSIONID的Cookie了,但是程序中仍然可以读写其他的Cookie。


会话状态保持,JSESSIONID,COOKIE之间的关系

在服务器端,我们用惯了session.setAttribute("",userInfo)这样的一行代码,估计你很少想到:服务器与浏览器之间是如何保持会话状态的。好了,先引用一些文章的精彩片段:

 

http://www.xxx.com/xxx_app;jsessionid=xxxxxxxxxx?a=x&b=x。

这跟一般的url基本一样,只有一个地方有区别,那就是“;jessionid=xxxxxxxx”。这个参数有时候有,有时候又没有,说它是参数可又跟一般传递的参数不同,它是紧跟在url后面用分号来分隔的,用一般的request.getParameter()方法还取不到jsessionid

 

启动你的tomcat,打开FireFox(爱得不得了,一定要安装FireBug),输入localhost就行,打开firebug,点网络,你会看到,浏览器与服务器会话的信息,给出浏览器

(1)第一次请求服务器:

浏览器的请求头信息

Hostlocalhost
User-AgentMozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6
Accepttext/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Languagezh-cn,zh;q=0.5
Accept-Encodinggzip,deflate
Accept-CharsetGB2312,utf-8;q=0.7,*;q=0.7
Keep-Alive115
Connection

keep-alive

 

服务器响应头信息

ServerApache-Coyote/1.1
Set-CookieJSESSIONID=64D21B4D69DFB3041B6375C1932BD6CB; Path=/
Content-Typetext/html;charset=UTF-8
Content-Languagezh-CN
Content-Length242
DateMon, 28 Jun 2010 02:35:29 GMT

(2)第二次请求服务器:

浏览器的请求头信息

Hostlocalhost
User-AgentMozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6
Accepttext/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Languagezh-cn,zh;q=0.5
Accept-Encodinggzip,deflate
Accept-CharsetGB2312,utf-8;q=0.7,*;q=0.7
Keep-Alive115
Connectionkeep-alive
CookieJSESSIONID=64D21B4D69DFB3041B6375C1932BD6CB

服务器响应头信息

ServerApache-Coyote/1.1
Content-Typetext/html;charset=UTF-8
Content-Languagezh-CN
Content-Length242
DateMon, 28 Jun 2010 02:37:51 GMT

重复第三次,每四次...第N次请求服务器,浏览器和服务器的请求头信息都是与第二次请求服务器是一样的。


(3)但是,如果你在服务器端加入如下一行代码:

Log.info("SessionId:" + request.getSession().getId());

你会看到,当你第一次请求服务器时,就会默认有一个新的session被创建,而且在session的有效时间范围内,这个输出值是不会变的,否则,服务器会重新创建一个session,自然,sessionId也就不同了,这段代码的输出自然也会不同了。

 

(4)你必须注意这一点:你用的是浏览器与服务器通信:

有一些事情是浏览器帮助我们去做了,那就是:当你第一次与服务器通信时,浏览器会保存服务器返回的Set-Cookie这个健的值(JSESSIONID=64D21B4D69DFB3041B6375C1932BD6CB),只要你不关闭浏览器(彻底关闭,关闭选项卡不算),浏览器会从第二次向服务器发出请求开始,一直带上这个键值对,发给服务器。服务器就会知道,这是同一个人(同一个会话)发起的请求。

 

(5)我们再注意一下:request.setAttribute("sysuser",userInfo)这句话:

当你第一次请求服务器时,这句代码会根据服务器默认产生的session得到ID,并与sysuser=userInfo这个键值对挂上钩(当然,userInfo可以是任何对象),保证唯一关联,检测用户是否登录就是这样实现的。

我一定要声明一点:保持一个会话与用户是否登录是没有任何关系的。

 

(6)再次引深一下,如果你用的不是浏览器,比如说做J2ME开发,怎样保持会话呢?

(1)在你写完这行代码后:HttpConnection hc = (HttpConnection)Connector.open(httpURL),加入以下代码:(Constant.sessionID只是一个静态变量)

[java] view plaincopyprint?

  1. //在与服务器通信前设置sessionId,维持唯一的一个会话  

  2. if (Constant.sessionID != null) {  

  3.    hc.setRequestProperty("Cookie", AppContext.CurrentAppContext.sessionID);  

  4. }  

//在与服务器通信前设置sessionId,维持唯一的一个会话 if (Constant.sessionID != null) {   hc.setRequestProperty("Cookie", AppContext.CurrentAppContext.sessionID); }

 

(2) A:只向服务器读数据,不向服务写数据,B:先向服务器写数据,再从服务器读数据

对于这两种情况,只要你第一次打开openDataInputStream(),这可以加入以下代码(Constant.isLogin只是一个静态变量boolean):

 

[java] view plaincopyprint?

  1. //每次与服务器通信后,保存 sessionId  

  2. String cookie = hc.getHeaderField("Set-Cookie");  

  3. if (cookie != null) {  

  4.       String jsessionId = cookie.substring(0,cookie.indexOf(";"));  

  5.       if(Constant.sessionID != null && !Constant.sessionID.equals(jsessionId) && Constant.isLogin ){  

  6.           Log.info("sessionid超时, will get new sessionid, but you must login again");  

  7.           //设置为未登录状态  

  8.           Constant.isLogin = false;  

  9.       }  

  10.       Constant.sessionID = jsessionId;  

  11. }   

//每次与服务器通信后,保存 sessionId String cookie = hc.getHeaderField("Set-Cookie"); if (cookie != null) {      String jsessionId = cookie.substring(0,cookie.indexOf(";"));      if(Constant.sessionID != null && !Constant.sessionID.equals(jsessionId) && Constant.isLogin ){          Log.info("sessionid超时, will get new sessionid, but you must login again");          //设置为未登录状态          Constant.isLogin = false;      }      Constant.sessionID = jsessionId; } 

这样就可以保持一个会话了。


(7)最后,关于URL重定向

引用一段话:sun帮我们想到了,所以提供了2个方法来使事情变得简单:response.encodeURL()和response.encodeRedirectURL()。这2个方法会判断cookie是否可用,如果禁用了会解析出url中的jsessionid,并连接到指定的url后面,如果没有找到jessionid会自动帮我们生成一个。至于为什么要有2个方法?这2个方法有什么不同?google了一下,说是这2个方法在判断是否要包含jsessionid的逻辑上会稍有不同。在调用 HttpServletResponse.sendRedirect前,应该先调用encodeRedirectURL()方法,否则可能会丢失 Sesssion信息。这2个方法的使用方法如:response.sendRedirect(response.encodeURL("/myapp /input.jsp"));。如果cookie没有禁用,我们在浏览器地址栏中看到的地址是这样的:/myapp/input.jsp,如果禁用了 cookie,我们会看到:/myapp /input.jsp;jsessionid=73E6B2470C91A433A6698C7681FD44F4。所以,我们在写web应用的时候,为了保险起见,应该在程序里的每一个跳转url上都使用这2个方法,来保证session的可用性。