I am trying to server a particular error page when session timeouts to my users.
For this i configured the error page on my Application's init method.
But this thing is not working.
I set up the session tiemout in 1 minute, after that nothing happen, I went through the logs, but wicket didn't throw any PageExpiredException.
When session timeouts wicket simply logs it as:
Session unbound: C061F4F21C41EDF13C66795DAC9EDD02
Removing data for pages in session with id 'C061F4F21C41EDF13C66795DAC9EDD02'
this is my init method in my customApplication
protected void init() {
super.init();
this.getApplicationSettings().setPageExpiredErrorPage(SessionExpiredPage.class);
...
...
}
my SessionExpiredPage.class
public class SessionExpiredPage extends TecnoAccionPage {
public SessionExpiredPage() {
this.setOutputMarkupId(true);
this.add(new Label("title", "SesiĆ³n Expirada"));
CSSLoader.get().appendCssUntil(this, SessionExpiredPage.class);
}
}
And i have a custom implementation of AbstractRequestCycleListener i override the OnException method But, when my session expire, I never pass in the "onException".
Thank You, best regards.
For some reason there is no PageExpiredException thrown by wicket, while it can reconstruct requested page, even if the session was expired.
So, there is another way to deal with this problem.
You have to override onRequestHandlerResolved method in your AbstractRequestCycleListener, to catch all incoming requests, and check there if incoming session id is outdated.
To check this, you must have list of the expired sessions in your app and catch unbound event to manage them.
This is going to be something like that:
public class YourApp extends WebApplication {
//synchronized list with ids
private List<String> unboundSessions = new CopyOnWriteArrayList<String>();
#Override
protected void init() {
super.init();
this.getApplicationSettings().setPageExpiredErrorPage(SessionExpiredPage.class);
//add request listener
getRequestCycleListeners().add(new AbstractRequestCycleListener() {
public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler) {
if (handler instanceof IPageRequestHandler) {
HttpServletRequest request = (HttpServletRequest) cycle.getRequest().getContainerRequest();
String sessionId = request.getRequestedSessionId();
//check whether the requested session has expired
boolean expired = sessionId != null && !request.isRequestedSessionIdValid();
//if session is not valid and it was really expired
if (expired && unboundSessions.contains(sessionId)) {
//then remove it from unbound list
unboundSessions.remove(sessionId);
//and throw exception
throw new PageExpiredException("Expired");
}
}
super.onRequestHandlerResolved(cycle, handler);
}
});
...
}
//this method called when any session is invalidated, so check your manual invalidating calls (if you ever do them)
#Override
public void sessionUnbound(String sessionId) {
super.sessionUnbound(sessionId);
if (!unboundSessions.contains(sessionId)) {
unboundSessions.add(sessionId);
}
}
}
Unbound sessions list needs for us to know, that user's session is really expired, since the expired variable in our listener could be also true when user just openes our site after redeploy, for example. His session is taken from his cookies and it could be already expired, but that would be weird to redirect him to SessionExpiredPage immediately.
It looks like a workaround, but it should work.
Related
So, I'm using HttpSessionListner and based what I found, there it said that using
server.servlet.session.timeout=1m
will disconned the inactive session i.e. invalading the session by calling sessionDestroyed method of the class implmenting HttpSessionListner.
The good part is that, yeah its working like it should be but the problem is, after 1 minute (which I set 1 minute because that's the miniumum requirement for tomcat) every session get destroyed no matter whether the session makes any requests to the server or not.
Is that how session timeout is suppose to work or I'm doing it in worng way.
Here is my code from the class implementing HttpSessionListner
#Component
public class SessionListner implements HttpSessionListener {
private static final Logger log = LoggerFactory.getLogger(SessionListner.class);
#Autowired
UserService userService;
#Override
public void sessionCreated(HttpSessionEvent se) {
// TODO Auto-generated method stub
HttpSession session = se.getSession();
session.setAttribute("sessionId", session.getId());
log.info(session.getId());
System.out.println(this.userService.getUser().size());
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
// TODO Auto-generated method stub
HttpSession httpSession = se.getSession();
if(userService.getUser().size() >0) {
for (int i = 0; i < this.userService.getUser().size(); i++) {
if (this.userService.getUser().get(i).getSessionId().equals(httpSession.getId())) {
this.userService.getUser().remove(this.userService.getUser().get(i));
}
}
}
httpSession.invalidate();
log.info("Destroying session");
}
}
I tried making changes in the frontend in a way I can make request to the server but well no matter how much I make request, the session get destroyed after assigned timeout time.
Oh, I finally find out what the problem is. So, what was happening is that when frontend makes a request to the server, new session was created for each request meaning that even if the browswer was open and making continuous requests to the server, the request was being made through a new session id rather than the one that was first created by sessionCreated method resulting in httpsessionlistner thinking that the session is inactive
Don't have the solution yet but the problem that I'm asking is solved.
I want to persist Tomcat's HttpSessions to disk so that it can be used in a scalable cloud environment. The point is that there will be a number of Tomcat nodes up (in a cloud PaaS) and clients can be directed to any of them. We want to persist and load the sessions from a shared disk unit.
I have configured the PersistentManager this way:
context.xml
<Manager className="org.apache.catalina.session.PersistentManager">
<Store className="org.apache.catalina.session.FileStore" directory="c:/somedir"/>
</Manager>
The problem is that sessions are, apparently, never flushed to disk.
I changed the <Manager> config adding maxIdleBackup:
<Manager className="org.apache.catalina.session.PersistentManager maxIdleBackup="1">
This way it takes almost a minute until I see the session persisted to disk. Oddly enough, the doc states that it should take around a second:
maxIdleBackup: The time interval (in seconds) since the last access to
a session before it is eligible for being persisted to the session
store, or -1 to disable this feature. By default, this feature is
disabled.
Other config:
Following the documentation I set the system property
org.apache.catalina.session.StandardSession.ACTIVITY_CHECK -> true
Is there a way to immediately flush the session to disk? Is is possible to make that any change in the session is also persisted right away?
UPDATE:
I have tried to force the passivation of the session and flushing to disk with maxIdleBackup="0" minIdleSwap="0" maxIdleSwap="1", but it still takes almost a minute.
You can also use this valve which is part of the Tomcat distribution (at least in version 8) :
<Valve className="org.apache.catalina.valves.PersistentValve"/>
This node has to be inserted before the <Manager className="org.apache.catalina.session.PersistentManager"> node in the context.xml file.
It will then use the store to maintain the session on each http request. Note that the documentation assumes that only one http request will be made by the same client at a time.
This will allow you to use non sticky session load balancer in front of your java ee servers.
I came across this because Tomcat was taking a minute to shutdown once I added the PersistentManager to the configuration, but it relates to your problem too:
The reason you it takes a minute to persist with the PersistentManager is because you haven't adjusted the processExpiresFrequency. This setting regulates how often the PersistentManager will run it's background processes to expire session, persist them, etc. The default value is 6. (See docs: http://tomcat.apache.org/tomcat-8.5-doc/config/manager.html#Standard_Implementation)
Per the code, this value is multiplied by engine.backgroundProcessorDelay, which you set on your <Engine> element. It's default value is 10. So 6*10 is 60 seconds. If you add processExpiresFrequency="1" on your <Manager> element, you'll see it will shutdown much quicker (10 seconds). If that's not fast enough, you can adjust the backgroundProcessorDelay to be lower too. You'll also still want to set maxIdleBackup to 1. You won't get absolutely immediate persistence, but it's very quick and doesn't require the self-described "ugly tweak" in the accepted answer.
(See comments about backgroundProcessorDelay on setMaxIdleBackup method in http://svn.apache.org/repos/asf/tomcat/tc8.5.x/tags/TOMCAT_8_5_6/java/org/apache/catalina/session/PersistentManagerBase.java)
I finally managed to solve this:
I extended org.apache.catalina.session.ManagerBase overriding every method that used the superclass' sessions map, so that it attacked a file (or cache) directly.
Example:
#Override
public HashMap<String, String> getSession(String sessionId) {
Session s = getSessionFromStore(sessionId);
if (s == null) {
if (log.isInfoEnabled()) {
log.info("Session not found " + sessionId);
}
return null;
}
Enumeration<String> ee = s.getSession().getAttributeNames();
if (ee == null || !ee.hasMoreElements()) {
return null;
}
HashMap<String, String> map = new HashMap<>();
while (ee.hasMoreElements()) {
String attrName = ee.nextElement();
map.put(attrName, getSessionAttribute(sessionId, attrName));
}
return map;
}
IMPORTANT:
load and unload methods must be left empty:
#Override
public void load() throws ClassNotFoundException, IOException {
// TODO Auto-generated method stub
}
#Override
public void unload() throws IOException {
// TODO Auto-generated method stub
}
You have to override startInternal and stopInternal to prevent Lifecycle errors:
#Override
protected synchronized void startInternal() throws LifecycleException {
super.startInternal();
// Load unloaded sessions, if any
try {
load();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardManager.managerLoad"), t);
}
setState(LifecycleState.STARTING);
}
#Override
protected synchronized void stopInternal() throws LifecycleException {
if (log.isDebugEnabled()) {
log.debug("Stopping");
}
setState(LifecycleState.STOPPING);
// Write out sessions
try {
unload();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardManager.managerUnload"), t);
}
// Expire all active sessions
Session sessions[] = findSessions();
for (int i = 0; i < sessions.length; i++) {
Session session = sessions[i];
try {
if (session.isValid()) {
session.expire();
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
} finally {
// Measure against memory leaking if references to the session
// object are kept in a shared field somewhere
session.recycle();
}
}
// Require a new random number generator if we are restarted
super.stopInternal();
}
The above allows to read always from the file (or cache) but what about the write operations?. For this, I extended org.apache.catalina.session.StandardSession overriding public void setAttribute(String name, Object value, boolean notify) and public void removeAttribute(String name, boolean notify).
Example:
#Override
public void setAttribute(String name, Object value, boolean notify) {
super.setAttribute(name, value, notify);
((DataGridManager)this.getManager()).getCacheManager().getCache("sessions").put(this.getIdInternal(), this);
}
#Override
public void removeAttribute(String name, boolean notify) {
super.removeAttribute(name, notify);
((DataGridManager)this.getManager()).getCacheManager().getCache("sessions").put(this.getIdInternal(), this);
}
IMPORTANT:
In our case the real session backup ended up being a cache (not a file) and when we read the extended Tomcat session from it (in our ManagerBase impl class) we had to tweak it in an kind of ugly way so that everything worked:
private Session getSessionFromStore(String sessionId){
DataGridSession s = (DataGridSession)cacheManager.getCache("sessions").get(sessionId);
if(s!=null){
try {
Field notesField;
notesField = StandardSession.class.getDeclaredField("notes");
notesField.setAccessible(true);
notesField.set(s, new HashMap<String, Object>());
s.setManager(this);
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
throw new RuntimeException(e);
}
}
return s;
}
My original HttpSessionListener code:
public class SessionListener implements HttpSessionListener {
#Override
public void sessionDestroyed(HttpSessionEvent event) {
final Object user = event.getSession().getAttribute("user");
if (user != null && user insteaceof User) {
UserUtils.deleteUser((User) user);
}
}
}
and my web.xml
<filter>
<filter-name>ObjectifyFilter</filter-name>
<filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ObjectifyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
When a session timeout event happens it throws:
WARNING: Problem scavenging sessions
java.lang.IllegalStateException: You have not started an Objectify context. You are probably missing the ObjectifyFilter. If you are not running in the context of an http request, see the ObjectifyService.run() method.
at com.googlecode.objectify.ObjectifyService.ofy(ObjectifyService.java:44)
at com.learnkeeper.server.OfyService.ofy(OfyService.java:61)
at com.learnkeeper.server.UserUtils.deleteUser(UserUtils.java:28)
at com.learnkeeper.server.SessionListener.sessionDestroyed(SessionListener.java:36)
at org.mortbay.jetty.servlet.AbstractSessionManager.removeSession(AbstractSessionManager.java:669)
at org.mortbay.jetty.servlet.AbstractSessionManager$Session.timeout(AbstractSessionManager.java:926)
at org.mortbay.jetty.servlet.HashSessionManager.scavenge(HashSessionManager.java:285)
at org.mortbay.jetty.servlet.HashSessionManager.access$000(HashSessionManager.java:44)
at org.mortbay.jetty.servlet.HashSessionManager$2.run(HashSessionManager.java:219)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
I tried that (from this post How to resolve "You have not started an Objectify context" in JUnit?):
public class SessionListener implements HttpSessionListener {
private Closeable closeable;
#Override
public void sessionDestroyed(HttpSessionEvent event) {
final Object user = event.getSession().getAttribute("user");
if (user != null && user instanceof User) {
closeable = OfyService.begin();
UserUtils.deleteUser((User) user);
closeable.close();
}
}
}
And here is my OfyService class:
class OfyService {
static {
// Register all my Entities classes
ObjectifyService.register(User.class);
...
}
public static Closeable begin() {
return ObjectifyService.begin();
}
public static ObjectifyFactory factory() {
return ObjectifyService.factory();
}
public static Objectify ofy() {
return ObjectifyService.ofy();
}
}
but same stacktrace :(
So what did I miss?
thx
EDIT
to follow-up with #stickfigure
So I cleaned my project and re-run my use case and I get this stacktrace now:
WARNING: Problem scavenging sessions
java.lang.NullPointerException: No API environment is registered for this thread.
at com.google.appengine.api.datastore.DatastoreApiHelper.getCurrentAppId(DatastoreApiHelper.java:132)
at com.google.appengine.api.datastore.DatastoreApiHelper.getCurrentAppIdNamespace(DatastoreApiHelper.java:148)
at com.google.appengine.api.datastore.Key.(Key.java:96)
at com.google.appengine.api.datastore.Key.(Key.java:78)
at com.google.appengine.api.datastore.KeyFactory.createKey(KeyFactory.java:54)
at com.google.appengine.api.datastore.KeyFactory.createKey(KeyFactory.java:47)
at com.googlecode.objectify.util.DatastoreUtils.createKey(DatastoreUtils.java:86)
at com.googlecode.objectify.impl.KeyMetadata.getRawKey(KeyMetadata.java:187)
at com.googlecode.objectify.impl.Keys.rawKeyOf(Keys.java:36)
at com.googlecode.objectify.impl.Keys.keyOf(Keys.java:29)
at com.googlecode.objectify.impl.LoaderImpl.entity(LoaderImpl.java:121)
at com.learnkeeper.server.UserUtils.deleteUser(UserUtils.java:28)
at com.learnkeeper.server.SessionListener.sessionDestroyed(SessionListener.java:40)
at org.mortbay.jetty.servlet.AbstractSessionManager.removeSession(AbstractSessionManager.java:669)
at org.mortbay.jetty.servlet.AbstractSessionManager$Session.timeout(AbstractSessionManager.java:926)
at org.mortbay.jetty.servlet.HashSessionManager.scavenge(HashSessionManager.java:285)
at org.mortbay.jetty.servlet.HashSessionManager.access$000(HashSessionManager.java:44)
at org.mortbay.jetty.servlet.HashSessionManager$2.run(HashSessionManager.java:219)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
I don't see any reason why that code should fail, although it can be written more elegantly:
public class SessionListener implements HttpSessionListener {
#Override
public void sessionDestroyed(HttpSessionEvent event) {
final Object user = event.getSession().getAttribute("user");
if (user != null && user instanceof User) {
try (Closable closable = OfyService.begin()) {
UserUtils.deleteUser((User) user);
}
}
}
I have many variations of this code in my applications and there are examples in the test cases - actually, all of Objectify's test cases rely on this pattern.
I would like to see the exact stacktrace generated when you run this code. It should be quite impossible to get that stacktrace if you have called begin() properly. You can look at the code in ObjectifyService.ofy() - it is quite simple. Doublecheck that the code you have deployed is the code you think you have deployed.
UPDATE: The new stacktrace is quite different, and indicates that GAE is not set up to perform API calls from that listener callback. It has nothing to do with Objectify; this is now a question for Google. I suggest writing a new question that focuses on that aspect and tagging it with GAE-related tags.
That said, my general advice is to avoid relying on this callback. Aside from technical issues like this, I would not trust it to be executed consistently in a distributed environment like GAE. If you want to expire an object, put a datestamp on it and cull anything older than a week (or whatever is reasonable).
Google App Engine Doesn't support session listeners. Session listeners may get invoke in local, but No API environment is registered for this thread. In production listeners will not even invoke.
Source #Ramesh V
https://stackoverflow.com/a/11152125/421563
Anyway thx #stickfigure
I recently discovered the magic of using MDCs when logging. It works perfectly.
I have the following method:
public static final String MDC_CLIENT="client";
public static final String MDC_SESSION="session";
public static final String MDC_DEVICE="device";
// Called for every request.
public static void request(final HttpServletRequest request) {
// The MDC is a thread-local storage accessable from the log formatter.
MDC.put(MDC_CLIENT, String.format("%s:%s", request.getRemoteHost(), request.getRemotePort()));
HttpSession session = request.getSession();
MDC.put(MDC_SESSION, session.getId());
MDC.put(MDC_DEVICE, (String)session.getAttribute("device"));
// Also record the context.
setContext(session.getServletContext());
}
This is called as the first action in every jsp. This allows me to keep track of the details of the session in the log nicely.
However - how do I know when to remove these map entries? What event should I watch for that will allow me to tidy up the map?
I am hosting under Tomcat. If it re-uses threads then I won't leak memory because these are essentially thread-local so each put will overwrite the old put from the last time the thread was used. If it doesn't - or I am hosted under something else that doesn't - I am essentially growing the map potentially forever - or at least until the host is restarted.
I think the essence of my question is - is there an event I can detect that indicates that a particular session or thread is finished with and about to be released.
Rather than putting the call in every JSP I would register a ServletRequestListener, which gets notified both when the request starts and when it finishes:
public class MDCListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent e) {
YourUtilityClass.request((HttpServletRequest)e.getServletRequest());
}
public void requestDestroyed(ServletRequestEvent e) {
YourUtilityClass.tearDown((HttpServletRequest)e.getServletRequest());
}
}
Alternatively you could use a filter which lets you wrap around the usual request processing flow:
void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
setupMDC();
chain.doFilter(request, response);
tearDownMDC();
}
Either way you simply register the relevant classes in web.xml and the container should take care of the rest.
In cases where the user has closed the browser and walked away, I want to log when the session times out. For example as a system induced log out as opposed to user request (I already have working and tested code to log a user requested logout).
Since the user isn't actively submitting requests (especially if it is just a matter of the now unused session timing out on the server) I don't think a filter is possible. Can this be done with a phase listener? If so can you provide some insight or a skeleton, or at least point me in the right direction on how this might be done.
My understanding is that the session on the server is still active until it times out or some other mechanism invalidates it. I am assuming therefore that a phase listener will also be able to tell if as part of your login method, you kill any existing session prior to the user logging in again with a fresh view, other machine, etc.
I am OK with research, but would like to at least start while pointed in the right direction. :)
On a second note: Is it possible to differentiate between a session time out and a view expired?
Thought I'd post the solution I ended up with based on the suggestions:
public class ElsSoulardSessionListener implements HttpSessionListener {
#EJB
private SessionLogger sessionLogger = new SessionLogger();
private SessionLogDTO sessionData = new SessionLogDTO();
private ElsDateTimeFunctions ts = new ElsDateTimeFunctions();
private static final Logger logger = Logger.getLogger(ElsSoulardSessionListener.class.getSimpleName());
#Override
public void sessionCreated(HttpSessionEvent se) {
// Nothing to do yet
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
logger.log(Level.FINE, "SESSION DESTROYED LISTENER");
HttpSession session = se.getSession();
finalizeUserSessionLog(session);
}
/**
* Utility method to finalize user's session log entry. Returns
* early if the session log isn't found or more than one is returned.
* #param session
*/
private void finalizeUserSessionLog(HttpSession session) {
String sessionId = session.getId();
LogoutReasonType logoutReason = (LogoutReasonType) session.getAttribute("logoutreason");
if (logoutReason == null) {
logoutReason = LogoutReasonType.SESSION_TIMEOUT;
}
try {
sessionData = sessionLogger.findBySessionId(sessionId);
} catch (NonexistentEntityException | UnexpectedResultSetSizeException ex) {
logger.log(Level.WARNING, " sessionDestroyed ", ex);
return;
}
Calendar now = ts.getUtcDateTimeAsCalendar();
sessionData.setLogoutTimestamp(now);
sessionData.setLogoutReason(logoutReason);
sessionLogger.update(sessionData);
}
}
If this helps you...
In our application we have extended HttpSessionListener and used sessionDestroyed method to log the event of session timeout.
and registered the same in web.xml as
<listener>
<listener-class>
com.ourpackage.OurHttpSessionListener
</listener-class>
</listener>
I believe which you only catch events of the servlet container through ServletFilters. PhaseListener only exist inside JSF "sessions", after servlet requests. Check the JSF life cycle to make sure. After, you can create another request for invalidate the "session" in JSF