Http session lifecycle in Tomcat

I have the task of showing the site administrator a list of usernames and how many tomcat sessions are currently being used by each user (along with some other support related information).

I save authenticated users as an application context attribute as follows (saving unnecessary data).

Hashtable<String, UserInfo> logins //maps login to UserInfo 

where UserInfo is defined as

 class UserInfo implements Serializable { String login; private transient Map<HttpSession, String> sessions = Collections.synchronizedMap( new WeakHashMap<HttpSession, String>() //maps session to sessionId ); ... } 

Each successful login stores a session on this sessions card. My implementation of the HttpSessionsListener in sessionDestroyed() removes the destroyed session from this map and, if session.size () == 0, removes UserInfo from logins .

From time to time I have 0 sessions for some users. Peer reviews and unit tests show that the code is correct. All sessions are serializable.

Is it possible that Tomcat offloads sessions from memory to the hard drive, for example. when is there a period of inactivity (session timeout set to 40 minutes)? Are there any other scenarios where sessions are "lost" from the point of view of the GC, but HttpSessionsListener.sessionDestroyed () was not called?

J2SE 6, Tomcat 6 or 7 standalone, the behavior is compatible with any OS.

+4
source share
3 answers

Do you find that the problem occurs after rebooting Tomcat? Tomcat will serialize active sessions to disk during a successful shutdown and then deserialize at startup - I'm not sure if this will cause your HttpSessionListener.sessionCreated () to be called because the session is not strictly created, just deserialized (this may be wrong (! ), but you can test quite easily).

Did you also compare your results with the statistics of the session of Tomcat managers? It tracks the number of active sessions and should be tied to your numbers, if not, you know that your code is incorrect.

Also, probably not related to your problem, but is there a good reason why you are using Hashtable and WeakHashMap? I tend to go with ConcurrentHashMap, if I need a Map implementation, its performance is much better.

+1
source

Since this question came close to the ideas of 5 thousand, I think it would be useful to provide an example of a working solution.

The approach described in the question is incorrect - it will not handle server reboots and will not scale. Here is the best approach.

Firstly, your HttpServlet should handle user logins and logins, something like these lines:

 public class ExampleServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action = req.getParameter("do"); HttpSession session = req.getSession(true); //simple plug. Use your own controller here. switch (action) { case "logout": session.removeAttribute("user"); break; case "login": User u = new User(session.getId(), req.getParameter("login")); //store user on the session session.setAttribute("user",u); break; } } } 

The bean user must be Serializable and must re-register when de-serializing:

 class User implements Serializable { private String sessionId; private String login; User(String sessionId, String login) { this.sessionId = sessionId; this.login = login; } public String getLogin() { return login; } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); //re-register this user in sessions UserAttributeListener.sessions.put(sessionId,this); } } 

You will also need an HttpAttributeListener to properly manage the session life cycle:

 public class UserAttributeListener implements HttpSessionAttributeListener { static Map<String, User> sessions = new ConcurrentHashMap<>(); @Override public void attributeAdded(HttpSessionBindingEvent event) { if ("user".equals(event.getName())) sessions.put(event.getSession().getId(), (User) event.getValue()); } @Override public void attributeRemoved(HttpSessionBindingEvent event) { if ("user".equals(event.getName())) ExampleServlet.sessions.remove(event.getSession().getId()); } @Override public void attributeReplaced(HttpSessionBindingEvent event) { if ("user".equals(event.getName())) ExampleServlet.sessions.put(event.getSession().getId(), (User)event.getValue()); } } 

Of course, you will need to register your listener in web.xml:

 <listener> <listener-class>com.example.servlet.UserAttributeListener</listener-class> </listener> 

After that, you can always access the static map in the UserAttributeListener to get an idea of ​​how many sessions are running, how many sessions each user is using, etc. Ideally, you will have a slightly more complex data structure requiring a separate singleton class with proper access methods. Using containers with a copy-on-write strategy at the same time can also be a good idea, depending on the use case.

+3
source

Instead of writing something from scratch, check out the psi probe.

http://code.google.com/p/psi-probe/

It may just be a simple fall that solves your problems.

+1
source

Source: https://habr.com/ru/post/1347827/


All Articles