设为首页 收藏本站
查看: 1492|回复: 0

[经验分享] Tomcat源码分析(九)--Session管理

[复制链接]

尚未签到

发表于 2015-8-9 11:33:00 | 显示全部楼层 |阅读模式
本系列转载自 http://blog.iyunv.com/haitao111313/article/category/1179996    在明白Tomcat的Session机制之前,先要了解Session,Cookie,JSESSIONID这几个概念。JSESSIONID是一个唯一标识号,用来标识服务器端的Session,也用来标识客户端的Cookie,客户端和服务器端通过这个JSESSIONID来一一对应。这里需要说明的是Cookie已经包含JSESSIONID了,可以理解为JSESSIONID是Cookie里的一个属性。让我假设一次客户端连接来说明我对个这三个概念的理解:

     Http连接本身是无状态的,即前一次发起的连接跟后一次没有任何关系,是属于两次独立的连接请求,但是互联网访问基本上都是需要有状态的,即服务器需要知道两次连接请求是不是同一个人访问的。如你在浏览淘宝的时候,把一个东西加入购物车,再点开另一个商品页面的时候希望在这个页面里面的购物车还有上次添加进购物车的商品。也就是说淘宝服务器会知道这两次访问是同一个客户端访问的。
     客户端第一次请求到服务器连接,这个连接是没有附带任何东西的,没有Cookie,没有JSESSIONID。服务器端接收到请求后,会检查这次请求有没有传过来JSESSIONID或者Cookie,如果没有JSESSIONID和Cookie,则服务器端会创建一个Session,并生成一个与该Session相关联的JSESSIONID返回给客户端,客户端会保存这个JSESSIONID,并生成一个与该JSESSIONID关联的Cookie,第二次请求的时候,会把该Cookie(包含JSESSIONID)一起发送给服务器端,这次服务器发现这个请求有了Cookie,便从中取出JSESSIONID,然后根据这个JSESSIONID找到对应的Session,这样便把Http的无状态连接变成了有状态的连接。但是有时候浏览器(即客户端)会禁用Cookie,我们知道Cookie是通过Http的请求头部的一个cookie字段传过去的,如果禁用,那么便得不到这个值,JSESSIONID便不能通过Cookie传入服务器端,当然我们还有其他的解决办法,url重写和隐藏表单,url重写就是把JSESSIONID附带在url后面传过去。隐藏表单是在表单提交的时候传入一个隐藏字段JSESSIONID。这两种方式都能把JSESSIONID传过去。
     下面来看Tomcat是怎么实现以上流程的。从Tomcat源码分析(二)--连接处理可知,连接请求会交给HttpProcessor的process方法处理,在此方法有这么几句:



[java] view plaincopyprint?

  • parseConnection(socket);  
  • parseRequest(input, output);//解析请求行,如果有jessionid,会在方法里面解析jessionid  
  • if (!request.getRequest().getProtocol()  
  •     .startsWith("HTTP/0"))  
  •     parseHeaders(input);//解析请求头部,如果有cookie字段,在方法里面会解析cookie,  
下面看parseRequest方法里面是怎么解析jessionid的,这种解析方式是针对url重写的:




[java] view plaincopyprint?

  • parseRequest方法:  
  • int semicolon = uri.indexOf(match);//match是“;JSESSIONID=”,即在请求行查找字段JSESSIONID  
  •         if (semicolon >= 0) {                                   //如果有JSESSIONID字段,表示不是第一次访问  
  •             String rest = uri.substring(semicolon + match.length());  
  •             int semicolon2 = rest.indexOf(';');  
  •             if (semicolon2 >= 0) {  
  •                 request.setRequestedSessionId(rest.substring(0, semicolon2));//设置sessionid  
  •                 rest = rest.substring(semicolon2);  
  •             } else {  
  •                 request.setRequestedSessionId(rest);  
  •                 rest = "";  
  •             }  
  •             request.setRequestedSessionURL(true);  
  •             uri = uri.substring(0, semicolon) + rest;  
  •             if (debug >= 1)  
  •                 log(" Requested URL session id is " +  
  •                     ((HttpServletRequest) request.getRequest())  
  •                     .getRequestedSessionId());  
  •         } else {                               //如果请求行没有JSESSIONID字段,表示是第一次访问。  
  •             request.setRequestedSessionId(null);  
  •             request.setRequestedSessionURL(false);  
  •         }  

代码没什么说的,看url有没有JSESSIONID,有就设置request的sessionid,没有就设置为null。有再看parseHeaders方法:



[java] view plaincopyprint?

  • parseHeaders方法:  
  • .....  
  • ....else if (header.equals(DefaultHeaders.COOKIE_NAME)) { //COOKIE_NAME的值是cookie  
  •                 Cookie cookies[] = RequestUtil.parseCookieHeader(value);  
  •                 for (int i = 0; i < cookies.length; i++) {  
  •                     if (cookies.getName().equals  
  •                         (Globals.SESSION_COOKIE_NAME)) {  
  •                         // Override anything requested in the URL  
  •                         if (!request.isRequestedSessionIdFromCookie()) {  
  •                             // Accept only the first session id cookie  
  •                             request.setRequestedSessionId  
  •                                 (cookies.getValue());//设置sessionid  
  •                             request.setRequestedSessionCookie(true);  
  •                             request.setRequestedSessionURL(false);  
  •                             if (debug >= 1)  
  •                                 log(" Requested cookie session id is " +  
  •                                     ((HttpServletRequest) request.getRequest())  
  •                                     .getRequestedSessionId());  
  •                         }  
  •                     }  
  •                     if (debug >= 1)  
  •                         log(" Adding cookie " + cookies.getName() + "=" +  
  •                             cookies.getValue());  
  •                     request.addCookie(cookies);  
  •                 }  
  •             }   
       代码主要就是从http请求头部的字段cookie得到JSESSIONID并设置到reqeust的sessionid,没有就不设置。这样客户端的JSESSIONID(cookie)就传到tomcat,tomcat把JSESSIONID的值赋给request了。这个request在Tomcat的唯一性就标识了。
     我们知道,Session只对应用有用,两个应用的Session一般不能共用,在Tomcat一个Context代表一个应用,所以一个应用应该有一套自己的Session,Tomcat使用Manager来管理各个应用的Session,Manager也是一个组件,跟Context是一一对应的关系,怎么关联的请参考Tomcat源码分析(一)--服务启动,方法类似。Manager的标准实现是StandardManager,由它统一管理Context的Session对象(标准实现是StandardSession),能够猜想,StandardManager一定能够创建Session对象和根据JSESSIONID从跟它关联的应用中查找Session对象。事实上StandardManager确实有这样的方法,但是StandardManager本身没有这两个方法,它的父类ManagerBase有这两个方法:



[java] view plaincopyprint?

  • ManagerBase类的findSession和createSession()方法  
  • public Session findSession(String id) throws IOException {  
  •         if (id == null)  
  •             return (null);  
  •         synchronized (sessions) {  
  •             Session session = (Session) sessions.get(id);//根据sessionid(即JSESSIONID)查找session对象。  
  •             return (session);  
  •         }  
  •     }  
  • public Session createSession() { //创建session对象  
  •         // Recycle or create a Session instance  
  •         Session session = null;  
  •         synchronized (recycled) {  
  •             int size = recycled.size();  
  •             if (size > 0) {  
  •                 session = (Session) recycled.get(size - 1);  
  •                 recycled.remove(size - 1);  
  •             }  
  •         }  
  •         if (session != null)  
  •             session.setManager(this);  
  •         else  
  •             session = new StandardSession(this);  
  •   
  •         // Initialize the properties of the new session and return it  
  •         session.setNew(true);  
  •         session.setValid(true);  
  •         session.setCreationTime(System.currentTimeMillis());  
  •         session.setMaxInactiveInterval(this.maxInactiveInterval);  
  •         String sessionId = generateSessionId();//使用md5算法生成sessionId  
  •         String jvmRoute = getJvmRoute();  
  •         // @todo Move appending of jvmRoute generateSessionId()???  
  •         if (jvmRoute != null) {  
  •             sessionId += '.' + jvmRoute;  
  •             session.setId(sessionId);  
  •         }  
  •         session.setId(sessionId);  
  •         return (session);  
  •     }  
      以上是StandardManager的管理Session的两个重要方法。这里有一个问题,Session是在什么时候生成的?仔细想想,我们编写servlet的时候,如果需要用到Session,会使用request.getSession(),这个方法最后会调用到HttpRequestBase的getSession()方法,所以这里有个重要的点:Session并不是在客户端第一次访问就会在服务器端生成,而是在服务器端(一般是servlet里)使用request调用getSession方法才生成的。但是默认情况下,jsp页面会调用request.getSession(),即jsp页面的这个属性默认是true的,编译成servlet后会调用request.getSession()。所以只要访问jsp页面,一般是会在服务器端创建session的。但是在servlet里就需要显示的调用getSession(),当然是在要用session的情况。下面看这个getSession()方法:



[java] view plaincopyprint?

  • HttpRequestBase.getSession()  
  •    调用---------------》  
  •     HttpRequestBase.getSession(boolean create)  
  •        调用 ----------------》  
  •             HttpRequestBase.doGetSession(boolean create){  
  •       if (context == null)  
  •             return (null);  
  •      
  •         // Return the current session if it exists and is valid  
  •         if ((session != null) && !session.isValid())  
  •             session = null;  
  •         if (session != null)  
  •             return (session.getSession());  
  •         // Return the requested session if it exists and is valid  
  •         Manager manager = null;  
  •         if (context != null)  
  •             manager = context.getManager();  
  •   
  •         if (manager == null)  
  •             return (null);      // Sessions are not supported  
  •   
  •         if (requestedSessionId != null) {  
  •             try {  
  •                 session = manager.findSession(requestedSessionId);//这里调用StandardManager的findSession方法查找是否存在Session对象  
  •             } catch (IOException e) {  
  •                 session = null;  
  •             }  
  •             if ((session != null) && !session.isValid())  
  •                 session = null;  
  •             if (session != null) {  
  •                 return (session.getSession());  
  •             }  
  •         }  
  •   
  •         // Create a new session if requested and the response is not committed  
  •         if (!create)  
  •             return (null);  
  •         if ((context != null) && (response != null) &&  
  •             context.getCookies() &&  
  •             response.getResponse().isCommitted()) {  
  •             throw new IllegalStateException  
  •               (sm.getString("httpRequestBase.createCommitted"));  
  •         }  
  •   
  •         session = manager.createSession();//这里调用StandardManager的创建Session对象  
  •         if (session != null)  
  •             return (session.getSession());  
  •         else  
  •             return (null);  
  • }      

        至此,Tomcat的Session管理的大部分东西也写的差不多了,这里没有写StandardManager和StandardSession两个类以及他们的实现接口,还有继承关系等,是因为觉得这篇文章已经够长了,而且他们跟跟其他标准组件也差不多,无非是实现的具体功能不一样,比如StandardSession还有过期处理等,不过它们也跟其他组件有各种关系,比如StandardManager就跟Context容器是关联的。有机会再细细的说Session管理器其他的东西(持久化和分布式)。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-96339-1-1.html 上篇帖子: tomcat-7.0.30安装及配置 下篇帖子: [转]启动tomcat时报Neither the JAVA_HOME nor the JRE_HOME environment variable is defi
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表