When developing a JSP application it's possible to define a session timeout value, say 30 minutes.
After that timeout, the session object is destroyed.
Moreover I can programmatically invalidate a session calling session.invalidate() .
Since I'm saving a complex Java object inside the HTTP session, before invalidate the session or let it expire by the tomcat app server, I need to call a saved object method to release some memory. Of course I can do it programmatically when the user click a logout button.
What I would like to do is intercepting the Tomcat app server when it is going to destroy all expired sessions (30 minutes or custom), so that I can pre-process Java objects saved in the session calling a specific method to release memory.
Is it possible?
Yes, that's possible. You could use HttpSessionListener and do the job in sessionDestroyed() method,
#WebListener
public class MyHttpSessionListener implements HttpSessionListener {
#Override
public void sessionDestroyed(HttpSessionEvent event) {
// Do here the job.
}
// ...
}
Or you could let the complex object which is been stored as a session attribute implement the HttpSessionBindingListener and do the job in valueUnbound() method.
public class YourComplexObject implements HttpSessionBindingListener {
#Override
public void valueUnbound(HttpSessionBindingEvent event) {
// Do here the job.
}
// ...
}
It will be called whenever the object is to be removed from the session (either explicitly by HttpSession#removeAttribute() or by an invalidation/expire of the session).
Related
I am using Spring Session 1.0.1. I need to execute some logic when the user logs out, and I need to rely on the HTTP session being invalidated to cover the case where the user fails to explicitly log out.
The standard Spring Security SessionDestroyedEvent includes any applicable SecurityContext, but the Spring Session version of SessionDestroyedEvent only contains the session id. By the time this event fires, the session is no longer held by the SessionRepository so it can't be looked up by id.
Is there any way to retrieve the SecurityContext from the expired session using Spring Session?
Unfortunately there is not. The problem is that at the time Redis fires the event, the session is already gone. Furthermore, the event received from Redis does not contain the original information. This means there is no way to retrieve the SecurityContext.
For updates on this please track spring-projects/spring-session/issues/4
For sring-session 1.1+ with Redis
https://docs.spring.io/spring-session/docs/current/reference/html5/#httpsession-httpsessionlistener
You must configure HttpSessionEventPublisher and after that spring-session will propagate sessionDestroy event
#Configuration
#EnableRedisHttpSession
public class RedisHttpSessionConfig {
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
// ...
}
So you can use standard spting SessionDestroyedEvent listener
#Component
public class SessionDestroyListener implements ApplicationListener<SessionDestroyedEvent> {
#Override
public void onApplicationEvent(SessionDestroyedEvent event) {
logger.debug("session destroyed {}", event.getId());
if(!event.getSecurityContexts().isEmpty()) {
...
}
}
}
I want to write a Hook in Java that is executed if the session of my Liferay 5.2.3 Portal times out.
I managed to write a Hook that is executed whenever the user clicks the logout link with the following setup in the liferay-hook.xml:
<hook>
<event>
<event-class>com.extensions.hooks.LogoutHook</event-class>
<event-type>logout.events.pre</event-type>
</event>
</hook>
However the Logout Hook does not get called if the session times out, but I need to execute the same method on a timeout. I did not find an event-type for a session timeout.
Is there a way to execute a Java-Method when the session times out and identify the User-ID of the ended session?
There is an event which will be triggered upon Session Expiry/TimeOut event of User Session,
# Servlet session destroy event
servlet.session.destroy.events = com.extensions.hooks.CustomPreSessionExpireAction
You can either add this property in liferay-hook.xml or portal.properties [Written in Hook] or portal-ext.properties.
And can be used as ,
public class CustomPreSessionExpireAction extends SessionAction {
#Override
public void run(HttpSession session) throws ActionException {
//Code
}
}
However, We can only use HttpSession here. So, you need to figure out the way to get userId here.
Thanks
I have a List<entity> ctx of my entity class in my java web application(tomcat8, servlet3), I keep one instance for each session(user) with my context(list), and a reference copy in user's sessions, like following.
javax.servlet.http.HttpSession sess=request.getSession(true);
//declaring and initializing the entity object
entity e=new entity();
e.timestamp=System.currentTimeMillis();
//keep the e with the session
sess.setAttribute("e",e);
//and a reference copy with another context
ctx.add(e);
Question:
I just want to know(an event, listener, ...) about user http session, in order to remove the entity object from the ctx when session gets expired(removed).Now how may I realized that a session is being expired from the server?
Just use a HttpSessionListener. It exists specially for that usage. Simply override the sessionDestroyed(HttpSessionEvent se) :
#Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
// do your processing
}
#Override
public void sessionCreated(HttpSessionEvent se) {
// empty implementation if you do not need it ...
}
Do not forget to declare it. Extract from javadoc : In order to receive these notification events, the implementation class must be either declared in the deployment descriptor of the web application, annotated with WebListener, or registered via one of the addListener methods defined on ServletContext.
I have a task to show to the site admin a list of user names and how many tomcat sessions are currently utilized by each user (along with some other support-related information).
I keep authenticated users as the application context attribute as follows (sparing unnecessary details).
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 the session into this sessions map.
My HttpSessionsListener implementation in sessionDestroyed() removes the destroyed session from this map and, if sessions.size() == 0, removes UserInfo from logins.
From time to time I have 0 sessions showing up for some users. Peer reviews and unit testing show that the code is correct. All sessions are serializable.
Is it possible that Tomcat offloads sessions from memory to the hard drive, e.g. when there is a period of inactivity (session timeout is set to 40 minutes)? Are there any other scenarios where sessions are 'lost' from GC point of view, but HttpSessionsListener.sessionDestroyed() wasn't invoked?
J2SE 6, Tomcat 6 or 7 standalone, behaviour is consistent on any OS.
As this question got close to 5k views, I think it would be beneficial to provide an example of a working solution.
The approach outlined in the question is wrong - it will not handle server restarts and will not scale. Here is a better approach.
First, your HttpServlet needs to handle user logins and logouts, something along 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 User bean has to be Serializable and has to re-register itself upon de-serialization:
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 handle session lifecycle properly:
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 UserAttributeListener to get an idea of how many sessions are running, how many sessions each user is using etc. Ideally, you would have a bit more complex data structure warranting its own separate singleton class with proper access methods. Using containers with copy-on-write concurrent strategy might also be a good idea, depending on the use case.
Instead of writing something from scratch, check out psi-probe.
http://code.google.com/p/psi-probe/
This may just be a simple drop in that solves your problems.
Do you find that you get the issue following a restart of Tomcat? Tomcat will serialize active sessions to disk during a successful shutdown and then deserialize on startup - I'm not sure whether this will result in a call to your HttpSessionListener.sessionCreated() as the session isn't strictly created, just deserialized (this may be not be correct(!), but can be tested fairly easily).
Have you also compared your results with the Tomcat managers session stats? It keeps track of the number of active sessions, and should tie up with your figures, if not, you know your code is wrong.
Also, probably unrelated to your issue, but is there a good reason why you are using Hashtable and WeakHashMap? I tend to go with ConcurrentHashMap if i need a thread safe Map implementation, its performance is much better.
When a client visits the webapp for the first time and/or the HttpSession is obtained for the first time via request.getSession(), the servlet container creates a new HttpSession object, generates a long and unique ID (which you can get by session.getId()), and store it in the server's memory. The servlet container also sets a Cookie in the Set-Cookie header of the HTTP response with JSESSIONID as its name and the unique session ID as its value.
As per the HTTP cookie specification (a contract a decent web browser and web server have to adhere to), the client (the web browser) is required to send this cookie back in subsequent requests in the Cookie header for as long as the cookie is valid (i.e. the unique ID must refer to an unexpired session and the domain and path are correct). Using your browser's built-in HTTP traffic monitor, you can verify that the cookie is valid (press F12 in Chrome / Firefox 23+ / IE9+, and check the Net/Network tab). The servlet container will check the Cookie header of every incoming HTTP request for the presence of the cookie with the name JSESSIONID and use its value (the session ID) to get the associated HttpSession from server's memory.
The HttpSession stays alive until it has not been used for more than the timeout value specified in <session-timeout>, a setting in web.xml. The timeout value defaults to 30 minutes. So, when the client doesn't visit the web app for longer than the time specified, the servlet container trashes the session. Every subsequent request, even with the cookie specified, will not have access to the same session anymore; the servlet container will create a new session.
On the client side, the session cookie stays alive for as long as the browser instance is running. So, if the client closes the browser instance (all tabs/windows), then the session is trashed on the client's side. In a new browser instance, the cookie associated with the session wouldn't exist, so it would no longer be sent. This causes an entirely new HTTPSession to be created, with an entirely new session cookie begin used.
I have the following implementation of HttpSessionlistener
public class SessionListener implements HttpSessionAttributeListener, HttpSessionListener {
public void attributeAdded(HttpSessionBindingEvent event) {
...
}
public void attributeRemoved(HttpSessionBindingEvent event) {
...
}
public void attributeReplaced(HttpSessionBindingEvent event) {
}
//HttpSesion creation & destruction
public void sessionCreated(HttpSessionEvent event) {
HttpSession session = event.getSession();
//log created time
}
public void sessionDestroyed(HttpSessionEvent event) {
HttpSession session = event.getSession();
long destroyedTime = System.currentTimeMillis();
//log destroyed time
}
}
Basically i log the session creation and destruction time.
But if the session is long (default to 30 minutes), and user closes browser meanwhile, the
sessionDestroyed
is not called ?
Why is that ?
Is there a workaround to log exactly the when the session was destroyed (when user closes the browser)? Should'nt be this be the browser's problem, to kill the session when it is closed ?
Is there any interface that i have to implement for this to work ?
Thank you !
How would the server know when the browser is closed or the tab closed? At that point the browser doesn't send anything to the server.
This is a fundamental part of HTTP - it's a request/response protocol, not a "permanently open conversation" where you can tell if one party leaves the conversation. Think of it as a series of telegrams rather than a phone call - and you can't tell when you've received the last telegram you're going to get.
You'll need to design your way round this - to avoid needing to know when the browser has been closed. There are some ugly hacks to work around it - making AJAX poll the server with a heartbeat message, for example - but changing the design is a better solution.
NOTE: As jwenting commented below, this it's not 100% safe at all. If the the onunload event does not get triggered by a closing event of the browser tab or window, then this would fail.
I had the same problem, and solve it using an intrusive JavaScript event:
window.onunload
Let me briefly explain, lets say you have a JavaScript function that post, using jQuery, the current Session ID to that Servlet to invalidate it, like this:
function safeexit(){
$.post("/SessionKillServlet", { SID = <%=session.getId()%> }, function(data){});
}
To call that function, you just need to bind it like these:
window.onunload = safeexit; //without the ()
Now, safeexit method will be called when the TAB or BROWSER WINDOW gets closed (or in a redirection).
Session objects live in the server and are controlled by the server. Only the server can create or destroy sessions. So if the client is closed the session lives until it expires. The client can only suggest to the server that it can destroy some session. This request must be explicit somehow. Hence, when you close a browser window, there's no implicit request to the server informing that it must destroy a given session.
With out a lot of work the browser does not inform the server that the window is closed, therefore Java can not know when to destroy the session. Hence the time out is in place and is the first the server knows that this session can be distroyed.
You could try and play an ajax call on the javascript event document.onunload
https://developer.mozilla.org/en/DOM/window.onunload but you will need to be sure that the user is not still within your site when you do this.
If you need the session to be destroyed when a browser window/tab is closed you might attach a JavaScript handler to the onunload event that makes some sort of AJAX call to a resource that call kill the session.
Note that the onunload event does not always fire so it's not totally trustworthy. One trusty way might be to use a "heartbeat" system.
there is some hacks for knows
following code for destroy session when user closes browser
client side:
<script src="jquery path"></script>
<script>
window.onunload = function(){ $.get("${request.contextPath}/logout"); }
</script>
server side: create request mapping for "/logout"
public void doGet(HttpServletRequest request, HttpServletResponse) {
request.getSession().invalidate();
System.out.println('destroy from logout on unload browser');
}
Following code is optional
use session listener when want to know when session destroyed
// in class
import javax.servlet.*;
import javax.servlet.http.*;
public class SesListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent se) {
System.out.println("Session created...");
}
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("Session destroyed...");
}
}
//-----------------------------------------
// in web.xml
<listener>
<listener-class>SesListener</listener-class>
</listener>
As Eric mentioned the unload event on the browser can call a javascript function, which in turn can access an url through a servlet that logs you out. You need not wait for the actual response from the servlet.
The web browser interacts with the server through the http protocol and that is stateless.
you can just verify if your session user , aren't null like :
if (session.getAttribute("user") != null )
sessionsetAttribute("user","null");