专栏首页用户2442861的专栏Session原理和Tomcat实现分析

Session原理和Tomcat实现分析

这篇文章挖掘Session的原理和tomcat实现机制。     由于HTTP是无状态的协议,客户程序每次都去web页面,都打开到web服务器的单独的连接,并且不维护客户的上下文信息。如果需要维护上下文信息,比如用户登录系统后,每次都能够知道操作的是此登录用户,而不是其他用户。对于这个问题,存在三种解决方案:cookie,url重写和隐藏表单域。 1、cookie    cookie是一个服务器和客户端相结合的技术,服务器可以将会话ID发送到浏览器,浏览器将此cookie信息保存起来,后面再访问网页时,服务器又能够从浏览器中读到此会话ID,通过这种方式判断是否是同一用户。

 1 请求:    2 POST /ibsm/LoginAction.do HTTP/1.1    3 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*    4 Referer: http://192.168.1.20:8080/crm/    5 Accept-Language: zh-cn    6 Content-Type: application/x-www-form-urlencoded    7 UA-CPU: x86    8 Accept-Encoding: gzip, deflate    9 User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2)   10 Host: 192.168.1.20:8080   11 Content-Length: 13   12 Connection: Keep-Alive   13 Cache-Control: no-cache   14 15 username=jack 16 17 响应:   18 HTTP/1.1 200 OK   19 Server: Apache-Coyote/1.1   20 Set-Cookie: JSESSIONID=3267A671BFEAA147A2383B7E083D4G7E; Path=/crm   21 Content-Type: text/html;charset=GBK   22 Content-Length: 436   23 Date: Sat, 10 June 2009 12:43:26 GMT

生成响应的时候,服务器向客户端发送cookie。cookie的属性是JSESSIONID,值是267A671BFEAA147A2383B7E083D4G7E。以后每次客户端请求时,都会附上此cookie,服务器端就可以读取到。

1    1. GET /ibsm/ApplicationFrame.frame HTTP/1.1   2    2. Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*   3    3. Accept-Language: zh-cn   4    4. UA-CPU: x86   5    5. Accept-Encoding: gzip, deflate   6    6. User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2)   7    7. Host: 192.168.1.20:8080   8    8. Connection: Keep-Alive   9    9. Cookie: JSESSIONID=267A671BFEAA147A2383B7E083D4G7E

服务器端根据读取到的JSESSIONID,在一个map里面查找其对应的session对象,这个map的key是jsessionid的值,value是session对象。 2、URL重写 重写这种方式,客户端程序在每个URL的尾部自动添加一些额外数据,这些数据以表示这个会话,比如 http://192.168.1.20:8080/crm/getuserprofile.html;jsessionid=abc123。URL重写的额外数据是服务器自动添加的,那么服务器是怎么添加的呢?Tomcat在返回Response的时候,检查JSP页面中所有的URL,包括所有的链接,和 Form的Action属性,在这些URL后面加上“;jsessionid=xxxxxx”。 添加url后缀的代码片段如下: org.apache.coyote.tomcat5.CoyoteResponse类的toEncoded()方法支持URL重写。   

1 StringBuffer sb = new StringBuffer(path); 2 if( sb.length() > 0 ) { // jsessionid can't be first. 3             sb.append(";jsessionid="); 4             sb.append(sessionId); 5         } 6         sb.append(anchor); 7         sb.append(query); 8 return (sb.toString());

从上面URL的实现原理可知,URL重写有一个缺点:在你的站点上不能有任何静态的HTML页面(至少静态页面中不能有任何链接到站点动态页面的链接)。因此,每个页面都必须使用servlet或 JSP动态生成。即使所有的页面都动态生成,如果用户离开了会话并通过书签或链接再次回来,会话的信息都会丢失,因为存储下来的链接含有错误的标识信息- 该URL后面的SESSION ID已经过期了。 3、隐藏表单域    这种方式借助html表单中的hidden来实现,适用特定的一个流程,但是不适用于通常意义的会话跟踪。 综上所述,session实现会话跟踪通常是cookie和url重写,如果浏览器不禁止cookie的话,tomcat优先使用cookie实现。 服务器端实现原理 Session在服务器端具体是怎么实现的呢?我们使用session的时候一般都是这么使用的: request.getSession()或者request.getSession(true)。 这个时候,服务器就检查是不是已经存在对应的Session对象,见HttpRequestBase类 doGetSession(boolean create)方法:

 1 if ((session != null) && !session.isValid())  2             session = null;  3 if (session != null)  4 return (session.getSession());  5  6  7 // Return the requested session if it exists and is valid  8         Manager manager = null;  9 if (context != null) 10             manager = context.getManager(); 11 if (manager == null) 12 return (null);      // Sessions are not supported 13 if (requestedSessionId != null) { 14 try { 15                 session = manager.findSession(requestedSessionId); 16             } catch (IOException e) { 17                 session = null; 18             } 19 if ((session != null) && !session.isValid()) 20                 session = null; 21 if (session != null) { 22 return (session.getSession()); 23             } 24         }

requestSessionId从哪里来呢?这个肯定是通过Session实现机制的cookie或URL重写来设置的。见HttpProcessor类中的parseHeaders(SocketInputStream input):

 1 for (int i = 0; i < cookies.length; i++) {  2 if (cookies[i].getName().equals  3                         (Globals.SESSION_COOKIE_NAME)) {  4 // Override anything requested in the URL  5 if (!request.isRequestedSessionIdFromCookie()) {  6 // Accept only the first session id cookie  7                             request.setRequestedSessionId  8                                 (cookies[i].getValue());  9                             request.setRequestedSessionCookie(true); 10                             request.setRequestedSessionURL(false); 11 12                         } 13                     } 14 }

或者HttpOrocessor类中的parseRequest(SocketInputStream input, OutputStream output)

 1 // Parse any requested session ID out of the request URI  2 int semicolon = uri.indexOf(match);  //match 是";jsessionid="字符串  3 if (semicolon >= 0) {  4             String rest = uri.substring(semicolon + match.length());  5 int semicolon2 = rest.indexOf(';');  6 if (semicolon2 >= 0) {  7                 request.setRequestedSessionId(rest.substring(0, semicolon2));  8                 rest = rest.substring(semicolon2);  9             } else { 10                 request.setRequestedSessionId(rest); 11                 rest = ""; 12             } 13             request.setRequestedSessionURL(true); 14             uri = uri.substring(0, semicolon) + rest; 15 if (debug >= 1) 16                 log(" Requested URL session id is " + 17                     ((HttpServletRequest) request.getRequest()) 18                     .getRequestedSessionId()); 19         } else { 20             request.setRequestedSessionId(null); 21             request.setRequestedSessionURL(false); 22         } 23

里面的manager.findSession(requestSessionId)用于查找此会话ID对应的session对象。Tomcat实现 是通过一个HashMap实现,见ManagerBase.java的findSession(String id):

1 if (id == null) 2 return (null); 3 synchronized (sessions) { 4             Session session = (Session) sessions.get(id); 5 return (session); 6         }

Session本身也是实现为一个HashMap,因为Session设计为存放key-value键值对,Tomcat里面Session实现类是StandardSession,里面一个attributes属性:

1 /** 2      * The collection of user data attributes associated with this Session. 3 */ 4 private HashMap attributes = new HashMap();

所有会话信息的存取都是通过这个属性来实现的。Session会话信息不会一直在服务器端保存,超过一定的时间期限就会被删除,这个时间期限可以在web.xml中进行设置,不设置的话会有一个默认值,Tomcat的默认值是60。那么服务器端是怎么判断会话过期的呢?原理服务器会启动一个线程,一直查询所有的Session对象,检查不活动的时间是否超过设定值,如果超过就将其删除。见StandardManager类,它实现了Runnable接口,里面的run方法如下:

 1 /**  2      * The background thread that checks for session timeouts and shutdown.  3 */  4 public void run() {  5  6 // Loop until the termination semaphore is set  7 while (!threadDone) {  8             threadSleep();  9             processExpires(); 10         } 11 12     } 13 14 /** 15      * Invalidate all sessions that have expired. 16 */ 17 private void processExpires() { 18 19 long timeNow = System.currentTimeMillis(); 20         Session sessions[] = findSessions(); 21 22 for (int i = 0; i < sessions.length; i++) { 23             StandardSession session = (StandardSession) sessions[i]; 24 if (!session.isValid()) 25 continue; 26 int maxInactiveInterval = session.getMaxInactiveInterval(); 27 if (maxInactiveInterval < 0) 28 continue; 29 int timeIdle = // Truncate, do not round up 30                 (int) ((timeNow - session.getLastUsedTime()) / 1000L); 31 if (timeIdle >= maxInactiveInterval) { 32 try { 33                     expiredSessions++; 34                     session.expire(); 35                 } catch (Throwable t) { 36                     log(sm.getString("standardManager.expireException"), t); 37                 } 38             } 39         } 40 41     }

Session信息在create,expire等事情的时候都会触发相应的Listener事件,从而可以对session信息进行监控,这些Listener只需要继承HttpSessionListener,并配置在web.xml文件中。如下是一个监控在线会话数的Listerner:

import java.util.HashSet; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener;  public class MySessionListener implements HttpSessionListener {        public void sessionCreated(HttpSessionEvent event) {               HttpSession session = event.getSession();              ServletContext application = session.getServletContext();                            // 在application范围由一个HashSet集保存所有的session               HashSet sessions = (HashSet) application.getAttribute("sessions");              if (sessions == null) {                     sessions = new HashSet();                     application.setAttribute("sessions", sessions);              }                            // 新创建的session均添加到HashSet集中               sessions.add(session);              // 可以在别处从application范围中取出sessions集合              // 然后使用sessions.size()获取当前活动的session数,即为“在线人数”       }        public void sessionDestroyed(HttpSessionEvent event) {              HttpSession session = event.getSession();               ServletContext application = session.getServletContext();               HashSet sessions = (HashSet) application.getAttribute("sessions");                            // 销毁的session均从HashSet集中移除              sessions.remove(session);       } }

以上就是我对session的研究,后面还会对session扩展和跨应用session共享等进行进一步的研究。本文系原创,转载请标明来源。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • SSD人脸检测以及FDDB检测结果分析

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haluoluo211/article/d...

    bear_fish
  • 【node.js】本地模式安装express:'express' 不是内部或外部命令,也不是可运行的程序或批处理文件。

    http://blog.csdn.net/mcpang/article/details/26612865

    bear_fish
  • UnsatisfiedLinkError: Error looking up function 'TessBaseAPICreate': /usr/lib/lib2.so.3.0.2:

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haluoluo211/article/details...

    bear_fish
  • 关于ORA-00020问题的反思(r2笔记第3天)

    今天在生产环境中查看alert日志,发现了如下的一段错误。这个错误确实没有太多需要解释的。很明显就是因为session leak的经典问题。 ORA-000...

    jeanron100
  • Spring-Session基于Redis管理Session【面试+工作】

    session同样是使用redis来做集中式存储,为了方便测试使用本地的6379端口redis,LettuceConnectionFactory是redis连接...

    奋斗蒙
  • express-session设置session详解

    用express web开发框架开发网站时,关于session的设置大致可以分为两种情况,一种是只要用户通过浏览器访问网站就会生成session,第二种是只有用...

    挥刀北上
  • Session和redis结合运用技巧

    服务端和客户端之间是通过session(会话)来连接沟通。当客户端的浏览器连接到服务器后,服务器就会建立一个该用户的session。每个用户的session都是...

    php007
  • 从Spring Session源码看Session机制的实现细节

    去年我曾经写过几篇和 Spring Session 相关的文章,从一个未接触过 Spring Session 的初学者视角介绍了 Spring Session ...

    kirito-moe
  • 【Oracle】-【v$session】v$session的SNIPED状态

    最近有个测试库刚出现的情况, 现象: 以前一直未出现过这种状态: 有的应用(这里部署的应用有100多个,且都是Tuxedo长连接数据库 的应用)每天第一次...

    bisal
  • PHP系列 | PHP Document 注释标记及规范 && PHP命名规范

    e.对于出现两次或两次以上的关键字,要通过ingore忽略掉多余的,只保留一个即可

    Tinywan

扫码关注云+社区

领取腾讯云代金券