Tomcat源码---Session的分析一
一,前面看了大致的tomcat的请求/响应,接下来的文章对tomcat里面的一些模块进行详细分析,从中学习其思想...
Session的功能(Session 对象可以存储特定用户会话所需的信息):
1,session是一个以bean的形式存在的,存储在内存中,特定用户可对其进行crud操作.
2,session是有生命周期的.
3,sesson是通过特定用户访问系统时,返回给一个jsessionid来进行识别用户的
----------------------------------------------------------------------------------------------------------------------
1,当客户端发送请求时,如果在头部文件中有传送jsessionid(生成jsessionid是在下面一部分讲),则执行以下步骤
执行到请求时会执行这个方法CoyoteAdapter#service...方法下的这步时if (postParseRequest(req, request, res, response))
对sessionid进行了操作,如下:
/** * Parse additional request parameters. */ protected boolean postParseRequest(org.apache.coyote.Request req, Request request, org.apache.coyote.Response res, Response response) throws Exception { //代码略 // Parse session Id(解析;url=;jsessionid=) parseSessionId(req, request); //代码略 // Parse session Id(解决cookie中所带的jsessionid=) parseSessionCookiesId(req, request); return true; }
1,parseSessiond(req,request)
/**
* Parse session id in URL. */ protected void parseSessionId(org.apache.coyote.Request req, Request request) { ByteChunk uriBC = req.requestURI().getByteChunk(); //分析url上是否带有jsessionid int semicolon = uriBC.indexOf(match, 0, match.length(), 0); if (semicolon > 0) { // Parse session ID, and extract it from the decoded request URI int start = uriBC.getStart(); int end = uriBC.getEnd(); int sessionIdStart = semicolon + match.length(); int semicolon2 = uriBC.indexOf(';', sessionIdStart); if (semicolon2 >= 0) { request.setRequestedSessionId (new String(uriBC.getBuffer(), start + sessionIdStart, semicolon2 - sessionIdStart)); // Extract session ID from request URI byte[] buf = uriBC.getBuffer(); for (int i = 0; i < end - start - semicolon2; i++) { buf[start + semicolon + i] = buf[start + i + semicolon2]; } uriBC.setBytes(buf, start, end - start - semicolon2 + semicolon); } else { request.setRequestedSessionId (new String(uriBC.getBuffer(), start + sessionIdStart, (end - start) - sessionIdStart)); uriBC.setEnd(start + semicolon); } //如果有jsessionid,设置到request中 request.setRequestedSessionURL(true); } else { request.setRequestedSessionId(null); request.setRequestedSessionURL(false); } }
2,parseSessionCookiesId(req, request);
/** * Parse session id in cookie */ protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) { // If session tracking via cookies has been disabled for the current // context, don't go looking for a session ID in a cookie as a cookie // from a parent context with a session ID may be present which would // overwrite the valid session ID encoded in the URL Context context = (Context) request.getMappingData().context; if (context != null && !context.getCookies()) return; // Parse session id from cookies //获取cookies Cookies serverCookies = req.getCookies(); int count = serverCookies.getCookieCount(); if (count <= 0) return; for (int i = 0; i < count; i++) { ServerCookie scookie = serverCookies.getCookie(i); if (scookie.getName().equals(Globals.SESSION_COOKIE_NAME)) { // Override anything requested in the URL if (!request.isRequestedSessionIdFromCookie()) { // Accept only the first session id cookie convertMB(scookie.getValue()); request.setRequestedSessionId (scookie.getValue().toString()); request.setRequestedSessionCookie(true); request.setRequestedSessionURL(false); if (log.isDebugEnabled()) log.debug(" Requested cookie session id is " + request.getRequestedSessionId()); } else { if (!request.isRequestedSessionIdValid()) { // Replace the session id until one is valid convertMB(scookie.getValue()); //在进行查找jsessionid如果有的话,设置进request request.setRequestedSessionId (scookie.getValue().toString()); } } } } }
由以上可知如果客户端有访问过系统的话,客户端的jsessionid将被设置进request中
----------------------------------------------------------------------------------------
在StandardManager是用来管理Session类的,首先是StandardManager的实例化是在
StandardContext#start
// Acquire clustered manager Manager contextManager = null; if (manager == null) { if ( (getCluster() != null) && distributable) { try { contextManager = getCluster().createManager(getName()); } catch (Exception ex) { log.error("standardContext.clusterFail", ex); ok = false; } } else { contextManager = new StandardManager(); } }
-------------------------------------------------------------------------------
现在查看的是Session的生成过程..request.getSession()
/** * Return the session associated with this Request, creating one * if necessary. */ public HttpSession getSession() { Session session = doGetSession(true); if (session != null) { return session.getSession(); } else { return null; } }
doGetSession(true)
protected Session doGetSession(boolean create) { // There cannot be a session if no context has been assigned yet 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); // Return the requested session if it exists and is valid //StandardContext#start时进行的初始化 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); } catch (IOException e) { session = null; } if ((session != null) && !session.isValid()) session = null; if (session != null) { //更新时间,使其生命周期重设为30(默认) session.access(); return (session); } } // 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("coyoteRequest.sessionCreateCommitted")); } // Attempt to reuse session id if one was submitted in a cookie // Do not reuse the session id if it is from a URL, to prevent possible // phishing attacks //判断session cookie的存储路径 if (connector.getEmptySessionPath() && isRequestedSessionIdFromCookie()) { //进行创建 session = manager.createSession(getRequestedSessionId()); } else { session = manager.createSession(null); } // 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.getIdInternal()); configureSessionCookie(cookie); //在响应时,返回session cookie,这样在客户端就接收到一个jsessionid response.addCookieInternal(cookie, context.getUseHttpOnly()); } if (session != null) { //如上 session.access(); return (session); } else { return (null); } }
由上面的代码可以看出,sesion是先查找再创建,,,
ManagerBase#findSession
public Session findSession(String id) throws IOException { if (id == null) return (null); //sessions(ConcurrentHashMap<String, Session>)进行读取 return (Session) sessions.get(id); }
#createSession
public Session createSession(String sessionId) { // Recycle or create a Session instance //进行创建 Session session = createEmptySession(); // Initialize the properties of the new session and return it //进行初始化 session.setNew(true); session.setValid(true); session.setCreationTime(System.currentTimeMillis()); session.setMaxInactiveInterval(this.maxInactiveInterval); if (sessionId == null) { //产生一个jsessionid sessionId = generateSessionId(); // FIXME WHy we need no duplication check? /* synchronized (sessions) { while (sessions.get(sessionId) != null) { // Guarantee // uniqueness duplicates++; //产生一个jsessionid sessionId = generateSessionId(); } } */ // FIXME: Code to be used in case route replacement is needed /* } else { String jvmRoute = getJvmRoute(); if (getJvmRoute() != null) { String requestJvmRoute = null; int index = sessionId.indexOf("."); if (index > 0) { requestJvmRoute = sessionId .substring(index + 1, sessionId.length()); } if (requestJvmRoute != null && !requestJvmRoute.equals(jvmRoute)) { sessionId = sessionId.substring(0, index) + "." + jvmRoute; } } */ } session.setId(sessionId); sessionCounter++; return (session);
故一整个流程是:
当客户端有请求session时request.getSession(),并生成一个jsessionid cookie返回给客户端,以后每次访问都得带着这个jsessionid过来,进行对ConcurrentHashMap<String, Session>进行识别.
------------------------------------------------------------------------------------------------------------------
还有一个问题就是当服务器突然中断掉时,重新启动时,未过期的session是可以重新加载的...
StandardManager#start()方法。最后调用了#load()方法.
public void load() throws ClassNotFoundException, IOException { if (SecurityUtil.isPackageProtectionEnabled()){ try{ AccessController.doPrivileged( new PrivilegedDoLoad() ); } catch (PrivilegedActionException ex){ Exception exception = ex.getException(); if (exception instanceof ClassNotFoundException){ throw (ClassNotFoundException)exception; } else if (exception instanceof IOException){ throw (IOException)exception; } if (log.isDebugEnabled()) log.debug("Unreported exception in load() " + exception); } } else { doLoad(); } }
# doLoad()
protected void doLoad() throws ClassNotFoundException, IOException { if (log.isDebugEnabled()) log.debug("Start: Loading persisted sessions"); // Initialize our internal data structures sessions.clear(); // Open an input stream to the specified pathname, if any // %CATALINA_HOME%/localhost/%app_name% /SESSIONS.ser文件,每一个应用下都有这个sessions.ser //可以从这里还原未过期的session File file = file(); if (file == null) return; if (log.isDebugEnabled()) log.debug(sm.getString("standardManager.loading", pathname)); FileInputStream fis = null; ObjectInputStream ois = null; Loader loader = null; ClassLoader classLoader = null; try { fis = new FileInputStream(file.getAbsolutePath()); BufferedInputStream bis = new BufferedInputStream(fis); if (container != null) loader = container.getLoader(); if (loader != null) classLoader = loader.getClassLoader(); if (classLoader != null) { if (log.isDebugEnabled()) log.debug("Creating custom object input stream for class loader "); ois = new CustomObjectInputStream(bis, classLoader); } else { if (log.isDebugEnabled()) log.debug("Creating standard object input stream"); ois = new ObjectInputStream(bis); } } catch (FileNotFoundException e) { if (log.isDebugEnabled()) log.debug("No persisted data file found"); return; } catch (IOException e) { log.error(sm.getString("standardManager.loading.ioe", e), e); if (ois != null) { try { ois.close(); } catch (IOException f) { ; } ois = null; } throw e; } // Load the previously unloaded active sessions synchronized (sessions) { try { Integer count = (Integer) ois.readObject(); int n = count.intValue(); if (log.isDebugEnabled()) log.debug("Loading " + n + " persisted sessions"); for (int i = 0; i < n; i++) { StandardSession session = getNewSession(); session.readObjectData(ois); session.setManager(this); sessions.put(session.getIdInternal(), session); session.activate(); sessionCounter++; } } catch (ClassNotFoundException e) { log.error(sm.getString("standardManager.loading.cnfe", e), e); if (ois != null) { try { ois.close(); } catch (IOException f) { ; } ois = null; } throw e; } catch (IOException e) { log.error(sm.getString("standardManager.loading.ioe", e), e); if (ois != null) { try { ois.close(); } catch (IOException f) { ; } ois = null; } throw e; } finally { // Close the input stream try { if (ois != null) ois.close(); } catch (IOException f) { // ignored } // Delete the persistent storage file if (file != null && file.exists() ) file.delete(); } } if (log.isDebugEnabled()) log.debug("Finish: Loading persisted sessions"); }