In the previous sections we analyzed Tomcat's start-up, shutdown, request processing process, Tomcat's classloader mechanism, and this article will then analyze Tomcat's session management aspects.
Before we start, let's take a look at the overall structure and familiarize ourselves with the overall structure, and we'll analyze the source code step-by-step. The class diagram for the Tomcat session phase light is as follows:
We can see that each standardcontext is associated with a manager, and by default the manager's implementation class is Standardmanager, and Standardmanager internally aggregates multiple sessions. Where standardsession is the default implementation class for the session, When we call Request.getsession, Tomcat Standardsessionfacade this appearance class to return the Standardsession wrapper.
After the overall structure is clear, let's analyze it further through the source code. Let's start with the GetSession method of request.
Org.apache.catalina.connector.request#getsession
Public HttpSession getsession () { Session session = Dogetsession (true); if (session = = null) { return null; } return session.getsession ();}
from the above code, we can see first call the Dogetsession method to get the session, and then call the session of the GetSession method to return HttpSession, Then let's look at the Dogetsession method:
Org.apache.catalina.connector.request#dogetsessionprotected 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//1 manager manager = NULL; if (context = null) {manager = Context.getmanager (); } if (manager = = null) {return (NULL); Sessions is not supported}//2 if (Requestedsessionid! = null) {try {session = Manager. Findsession (Requestedsessionid); } catch (IOException e) {session = NULL; } if ((Session! = NULL) &&!session.isvalid ()) {session = NULL; } if (session! = NULL) { Session.access (); return (session); }}//Create a new session if requested and the response is not committed//3 if (!create) {return ( NULL); if (context! = NULL) && (response! = null) && Context.getservletcontext (). geteffectivesession Trackingmodes (). Contains (Sessiontrackingmode.cookie) && response.getresponse (). iscommitted ()) {throw new Illegalsta Teexception (sm.getstring ("coyoterequest.sessioncreatecommitted")); }//attempt to reuse session ID if one were submitted in a cookie//does not reuse the session ID if it's from a URL , to prevent possible//phishing attacks//with the SSL session ID if one is present. 4 if (("/". Equals (Context.getsessioncookiepath ()) && Isrequestedsessionidfromcookie ()) | | request EDSESSIONSSL) {session = Manager.createsession (Getrequestedsessionid ()); } else {session = Manager.createsession (NULL); }//Creating a new session cookie based on that session if (session! = NULL) && (GetContext ()! = null) && GetContext (). Getservletcontext (). Geteffectivesessiontrackingmodes (). Contains (Sessiontrackingmode.cookie)) {//5 Cookie cookie = Applicationsessioncookieconfig.createsessioncookie (context, Session.getidin Ternal (), issecure ()); Response.addsessioncookieinternal (cookie); } if (session = = NULL) {return null; } session.access (); return session;}
Let's focus on the following, where the numbers are marked in the code above:
- Callout 1 (line 17th) first obtains the corresponding Manager object from the Standardcontext, by default, this place obtains is actually the Standardmanager instance.
- Callout 2 (line 26th) Gets the session from the manager according to Requestedsessionid, and if the session is invalidated, the session is NULL to create a new session below. If the session is not empty, the session's access time is annotated by calling the session's access method and then returned.
- Callout 3 (line 43rd) to determine the parameters passed, if False, then directly return null, which is actually the case of the corresponding request.getsession (true/false), when passing false, if there is no session, then directly return null, Not new.
- Callout 4 (line 59th) calls the manager to create a new session, where the Standardmanager method is called by default, and Standardmanager inherits Managerbase. Then the default is actually called the Managerbase method.
- Callout 5 (line 72nd) creates a cookie, and the name of the cookie is a familiar jsessionid, and the Jsessionid is actually configurable, which can be modified by the sessioncookiename of the context node. Like what....
After getting to the session through Dogetsession, We find that the Session.getsession method is called, and the implementation class of the session is standardsession, so let's look at Standardsession's GetSession method.
org.apache.catalina.session.standardsession#getsessionpublic HttpSession GetSession () {if (facade = = null) {if (securityutil.ispackageprotectionenabled ()) {final standardses Sion fsession = this; Facade = accesscontroller.doprivileged (new privilegedaction<standardsessionfacade> () { @Override public Standardsessionfacade Run () {return new Standardsessionfacade (fses sion); } }); } else {facade = new Standardsessionfacade (this); }} return (facade);}
With the code above, we can see that the wrapper class will return Standardsession wrapper by Standardsessionfacade. Here I think you should be familiar with the entire process created by the session.
Then we'll see how the Sesssion was destroyed. In the Tomcat boot process (three of the Tomcat source code reading series), we start a containerbackgroundprocessor thread after the container is started, which is started when the container is started, This thread invokes org.apache.catalina.core.containerbase#backgroundprocess periodically through the background, And the Backgroundprocess method will eventually call Org.apache.catalina.session.managerbase#backgroundprocess, Next we'll look at Manger's Backgroundprocess method.
org.apache.catalina.session.managerbase#backgroundprocesspublic void Backgroundprocess () { count = (count + 1)% processexpiresfrequency; if (count = = 0) processexpires ();}
in the above code, you need to note that by default backgroundprocess is run every 10 seconds (Standardengine constructs when the Backgroundprocessordelay is set to 10), And here we use processexpiresfrequency to control the frequency, for example, the value of processexpiresfrequency defaults to 6, then the equivalent of not a minute to run the Processexpires method. Next we'll look at Processexpires.
org.apache.catalina.session.managerbase#processexpirespublic void ProcessExpires () {Long TimeNow = System.currenttimemillis (); Session sessions[] = findsessions (); int expirehere = 0; if (log.isdebugenabled ()) log.debug ("Start expire Sessions" + getName () + "at" + TimeNow + "Sessioncount" + ses Sions.length); for (int i = 0; i < sessions.length; i++) {if (Sessions[i]!=null &&!sessions[i].isvalid ()) { expirehere++; }} Long Timeend = System.currenttimemillis (); if (log.isdebugenabled ()) log.debug ("End expire Sessions" + getName () + "Processingtime" + (Timeend-timenow) + "Expired Sessions:" + Expirehere); Processingtime + = (timeend-timenow);}
The above code is relatively simple, first find out the current context of all the session, and then call the session of the IsValid method, then we look at the session of the IsValid method.
Org.apache.catalina.session.standardsession#isvalidpublic Boolean isValid () { if (this.expiring) { return true; } if (!this.isvalid) { return false; } if (Activity_check && accesscount.get () > 0) { return true; } if (Maxinactiveinterval > 0) { long timenow = System.currenttimemillis (); int timeidle; if (last_access_at_start) { timeidle = (int) ((timenow-lastaccessedtime)/1000L); } else { Timeidle = (i NT) ((timenow-thisaccessedtime)/1000L); } if (Timeidle >= maxinactiveinterval) { expire (true); } } return (this.isvalid);}
View the above code, mainly by comparing the current time and the last access is greater than the time difference between the maximum inactivity interval, if greater than the call expire (true) method to the session extended processing. It is important to note that, by default, Last_access_at_start is false, and the reader can be modified by setting the system properties, and if Last_access_at_start is used, the processing time of the request itself will not be counted. For example, a request processing started at 10:00, the request processing took 1 minutes, then if the Last_access_at_start is true, then whether the calculation is extended, is from 10:00, rather than 10:01.
Next, let's look at the expire method, the code is as follows:
Org.apache.catalina.session.standardsession#expirepublic void Expire (Boolean notify) {//Check to see if expire are in Progress or has previously been called if (expiring | |!isvalid) return; Synchronized (this) {//Check again, now we is inside the sync so this code is only runs once//double Check Locking-expiring and isValid need to be volatile if (expiring | |!isvalid) return; if (manager = = null) return; Mark this session as "being expired"//1 expiring = true; Notify interested Application event listeners//Fixme-assumes We call listeners in reverse order Conte XT context = (context) Manager.getcontainer (); The call to expire () may not be been triggered by the webapp. Make sure the WebApp ' s class loader are set when calling the//listeners ClassLoader OLDTCCL = null; if (context.getloader () = null && Context.getloader (). getClassLoader ()! = null) {OLDTCCL = Thread.CurrentThread (). Getcontextclasslo Ader (); if (globals.is_security_enabled) {privilegedaction<void> pa = new PRIVILEGEDSETTCCL ( Context.getloader (). getClassLoader ()); Accesscontroller.doprivileged (PA); } else {Thread.CurrentThread (). Setcontextclassloader (Context.getloader (). Getclassl Oader ()); }} try {//2 Object listeners[] = Context.getapplicationlifecyclelisteners (); if (notify && (listeners! = null)) {Httpsessionevent event = new Httpsess Ionevent (GetSession ()); for (int i = 0; i < listeners.length; i++) {int J = (listeners.length-1)-I; if (! ( LISTENERS[J] instanceof Httpsessionlistener)) ContinuE Httpsessionlistener listener = (httpsessionlistener) listeners[j]; try {context.firecontainerevent ("beforesessiondestroyed", listener) ; Listener.sessiondestroyed (event); Context.firecontainerevent ("aftersessiondestroyed", listener); } catch (Throwable t) {exceptionutils.handlethrowable (t); try {context.firecontainerevent ("aftersessiondestroyed", L Istener); } catch (Exception e) {//Ignore} Manager.getco Ntainer (). GetLogger (). Error (Sm.getstring ("standardsession.sessionevent"), T); }}} Finally {if (OLDTCCL! = null) {if (globals.is_security_enabled) {privilegedact ion<void> pa = new PRIVILEGEDSETTCCL (OLDTCCL); Accesscontroller.doprivileged (PA); } else {Thread.CurrentThread (). Setcontextclassloader (OLDTCCL); }}} if (Activity_check) {accesscount.set (0); } setvalid (FALSE); Remove this session from our manager's active Sessions//3 Manager.remove (this, true); Notify interested Session event listeners if (Notify) {firesessionevent (Session.session_destroyed_ev ENT, NULL); }//Call the Logout method if (principal instanceof GenericPrincipal) {GenericPrincipal GP = (G Enericprincipal) Principal; try {gp.logout (); } catch (Exception e) {manager.getcOntainer (). GetLogger (). Error (Sm.getstring ("Standardsession.logoutfail"), E) ; }}//We have completed expire of this session expiring = false; Unbind any objects associated with this session//4 String keys[] = keys (); for (int i = 0; i < keys.length; i++) removeattributeinternal (Keys[i], notify); }}
The main process of the above code I have already marked the number, let us analyze it individually:
- Callout 1 (line 18th) marks the current session as extended
- Callout 2 (line 41st) The method of Httpsessionlistener listener.
- Callout 3 (line 89th) removes the current session from the manager
- Callout 4 (line 113th) removes the properties saved in the session.
Here we have been aware of the process of creating and destroying Tomcat with standardsession, in fact standardsession only implemented the memory session, and Tomcat supports persisting the session, and synchronization between nodes of the session cluster. We will analyze the contents later.
Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.
Tomcat Session Management mechanism (Tomcat source parsing seven)