HTTP是一种无状态的通信协议,每一次请求都是相互独立的,服务端无法识别之前的请求。对于Web应用程序,其活动取决于某种状态,例如用户登录。这时,使用HTTP要求它有能力在一个登录请求后为后续的请求提供登录信息。解决方案是使用cookie,由服务器返回给浏览器,浏览器缓存cookie数据并在每次请求时将cookie数据提交给服务器。Cookie在请求中以明文形式传输,大小限制为4KB。显然,将所有状态数据保存在浏览器中是不可靠的。主流的方法是:当浏览器发送请求时,服务器为用户分配一个标识符,返回并存储在浏览器的Cookies中,服务器内部维护一个全局的请求状态库,使用生成的标识符来关联状态每个请求的信息。浏览器后续发送的请求会将标识符提交给服务器,以获取上一次请求的状态信息。服务器为了方便管理,将整个过程称为会话,并抽象成一个Session类,用于标识和存储用户的信息或状态。接下来通过会话标识的解析和生成,Session的创建、销毁和持久化等,分析Tomcat的源码实现。使用的版本是6.0.53。1、解析会话标识Cookie作为最常用的会话跟踪机制,所有的Servlet容器都支持,Tomcat也不例外。在Tomcat中,存储会话标识符的cookie的标准名称是JSESSIONID。如果浏览器不支持cookies,也可以使用以下方法来记录标识符:URL重写:将其作为路径参数包含在url中,如/path;JSESSIONID=xxxURL请求参数:将sessionID添加为页面的查询参数在所有链接中,如/path?JSESSIONID=xxxFORM隐藏字段:隐藏字段用于存储表单中的值,当表单提交到服务器时,Tomcat实现从中提取JSESSIONIDURL重写路径和Cookie。在分析源码之前,先看一下cookie响应的关键信息和cookie请求的header字段://SetCookieHTTP/1.1200OKServer:Apache-Coyote/1.1Set-Cookie:JSESSIONID=56AE5B92C272EA4F5E0FBFEFE6936C91;Path=/examplesDate:Sun,12May201901:40:35GMT//提交CookieGET/examples/servlets/servlet/SessionExampleHTTP/1.1Host:localhost:8080Cookie:JSESSIONID=56AE5B92C272EA4F5E0FBFEFE6936C911.1RewritePathfromURL包含sessionID路径参数的URL如下:http:///localhost:8080/examples/SessionExample;JSESSIONID=1234;n=v/?x=x只是在匹配的分号和斜杠之间寻找JSESSIONID,这也是正确的,除了Tomcat操作字节,核心代码在CoyoteAdapter.parsePathParameters()方法中,这里不再贴出。1.2从Cookie头域触发Cookie解析的方法如下:CoyoteAdapter.service(Request,Response)└─CoyoteAdapter.postParseRequest(Request,Request,Response,Response)└─CoyoteAdapter.parseSessionCookiesId(Request,Request)└─Cookies.getCookieCount()└─Cookies.processCookies(MimeHeaders)└─Cookies.processCookieHeader(byte[],int,int)这个processCookieHeader是对bytes进行操作的,解析起来很不直观。Tomcat内部还有一个usedcharactermarkedasobsolete字符串解析的方法,有助于理解。代码如下:privatevoidprocessCookieHeader(StringcookieString){//多个cookie值用逗号分隔();//获取等号的位置inti=token.indexOf("=");if(i>-1){//获取name和value并去掉空格Stringname=token.substring(0,i).trim();Stringvalue=token.substring(i+1,token.length()).trim();//RFC2109andbug去掉两端的双引号"value=stripQuote(value);//来自内部cookiecache从池中获取一个ServerCookie对象ServerCookiecookie=addCookie();//设置name和valuecookie.getName().setString(name);cookie.getValue().setString(value);}else{//wehaveabadcookie....justletitgo}}}解析完之后,接下来就是在parseSessionCookiesId方法中遍历并尝试匹配名为JSESSIONID的Cookie。如果存在,则将其值设置为Request的requestedSessionId,并关联一个内部Session对象2.生成sessioncookies和session相关的cookies由Tomcat内部生成。在Servlet中使用Request.getSession()获取session对象时,会触发执行。核心代码:protectedSessiondoGetSession(booleancreate){...//创建Session实例if(connector.getEmptySessionPath()&&isRequestedSessionIdFromCookie()){//如果sessionID来自cookie,请复用ID,如果来自一个URL,请不要//重复使用会话ID,以防止可能的钓鱼session=manager.createSession(getRequestedSessionId());}else{session=manager.createSession(null);}//创建一个新的会话cookie基于Sessionif((session!=null)&&(getContext()!=null)&&getContext().getCookies()){StringscName=context.getSessionCookieName();if(scName==null){//默认JSESSIONIDscName=Globals.SESSION_COOKIE_NAME;}//新建CookieCookiecookie=newCookie(scName,session.getIdInternal());//设置pathdomainsecureconfigureSessionCookie(cookie);//添加到响应头域response.addSessionCookieInternal(cookie,context.getUseHttpOnly());}if(session!=null){session.access();return(session);}else{return(null);}}添加到响应头域,也就是给gen根据Cookie对象,按照开头描述的格式进行格式化。3、SessionSession是Tomcat内部的一个接口,是HttpSession的表象类,用于维护Web应用特定用户请求之间的状态信息。相关类图设计如下:关键类或接口的作用如下:Manager——管理Session池,不同的实现提供了具体的功能,如持久化、分发ManagerBase——实现了一些基础功能,如Session池,ID生成算法,易于扩展StandardManager-标准实现,提供跨该组件重新启动的简单会话持久性(例如,当整个服务器关闭和重新启动时,或当特定Web应用程序重新加载时)PersistentManagerBase-提供几种不同的持久性存储管理方法,如文件和数据库Store-提供持久化存储和加载会话和用户信息ClusterManager-集群会话管理接口,负责会话复制DeltaManager-增量复制会话数据到集群中的所有成员BackupManager-将数据只复制到一个备份节点,一个nd集群中的所有成员都可以看到这个节点。本文不分析集群复制的原理,只分析单机会话的管理。3.1创建Session在Servlet中使用Request.getSession()获取会话对象时,会创建一个StandardSession实例:publicSessioncreateSession(StringsessionId){//默认返回的是newStandardSession(this)实例Sessionsession=createEmptySession();//初始化属性session.setNew(true);session.setValid(true);session.setCreationTime(System.currentTimeMillis());//设置session的有效时间,单位秒,默认30分钟,一个负值表示永不过期session.setMaxInactiveInterval(((Context)getContainer()).getSessionTimeout()*60);if(sessionId==null){//生成一个sessionIDsessionId=generateSessionId();session.setId(sessionId);sessionCounter++;SessionTimingtiming=newSessionTiming(session.getCreationTime(),0);synchronized(sessionCreationTiming){sessionCreationTiming.add(timing);sessionCreationTiming.poll();}return(session);}关键在于session的生成身份标识。看看Tomcat的生成算法:随机得到16个字节将这些字节用MD5加密,又得到一个16字节的数组遍历新的字节数组,用每个字节的高4位和低4位分别生成一个16进制字符,得到一个32位十六进制字符串核心代码如下:protectedStringgenerateSessionId(){byterandom[]=newbyte[16];StringjvmRoute=getJvmRoute();Stringresult=null;//将结果渲染为十六进制数字符串StringBufferbuffer=newStringBuffer();do{intresultLenBytes=0;if(result!=null){//重复,重新生成buffer=newStringBuffer();duplicates++;}//sessionIdLength为16while(resultLenBytes
