From andrewm at object-craft.com.au Tue Feb 1 18:16:00 2011 From: andrewm at object-craft.com.au (Andrew McNamara) Date: Tue, 01 Feb 2011 18:16:00 +1100 Subject: [albatross-users] SimpleSessionApp, and session time out In-Reply-To: <1223542406.6618.57.camel@cancun.hanskramer.com> References: <1223542406.6618.57.camel@cancun.hanskramer.com> Message-ID: <20110201071600.C2BAA20225@longblack.object-craft.com.au> >I have a few questions and half baked solutions I want to have some feed >back on. I have to apologise profusely for not seeing your message earlier. I hope you haven't already given up in disgust. >First of all I would like to use the SimpleSessionApp class >instead of the RandomModularSessionApp class. (I tend to like to work >with classes) Perhaps this is a mistake, just be frank. There are several different styles of application you might choose from - Simple, Modular or RandomModular. The "Simple" app classes use classes for the pages, the "Modular" and "RandomModular" classes use python modules for the pages. SimpleSessionApp should be fine for your purposes. The "Random" application classes are used when you want users to be able to choose pages via the URL (for the other application schemes, the page name doesn't appear in the URL). >A) >Then I want to have a link in my template files for people to log out. >Something like this: (I also need these kind of links for other >purposes, I might come on in a later e-mail) > >Log Out If you really want to use links to drive actions, you might want to use one of the "Random" application classes (which are driven by URLs). The non Random application classes are better driven via form submission. >Clicking on it, the frame work should execute: >class LogoutPage: > > name = 'logout' > > def page_process(self, context): > context.remove_session() > context.set_page('login') > >Currently that didn't work. I found a solution by making some small >changes. First I edited in app.py the run method of class Application. >Just before self.load_page(ctx) I put the following command: As mentioned above, only the Random apps will invoke this page when they see a /logout URL, and the page_process() method is only called on form submission (too late, in this case - you probably would use page_enter() instead). If you use one of the other application models, and had a page containing a logout button: [...] [...] Then the page_process() method for that page might look like: def page_process(self, ctx): if ctx.req_equals('logout'): ctx.remove_session() ctx.set_page('login') Note, no need for a logout page in this case. > if os.environ.has_key('PATH_INFO'): > ctx.locals.__page__ = os.environ['PATH_INFO'][1:] > >This works fine, except that in the next page the URI is rendered as >/cgi-bin/application.py/logout and the user cannot log in. I fixed this >by editing cgiapp.py and changing > def get_uri(self): > return self.get_param('REQUEST_URI') >to > def get_uri(self): > return self.get_param('SCRIPT_NAME') > >Is this going to break anything? Is this a good solution? Or am I doing >something fundamentally wrong (by for instance using SimpleSessionApp). >If not, can I supply a patch? It shouldn't be necessary to do this sort of low-level stuff. >B) >Session time out that was something that was biting me too. I work for a >Pharmaceutical company (clinical trail) and they really hate to see >stack traces. So when a session times out, instead of the stack trace I >want to refer it to a TimeOut page. (in the future I might want to >implement something that browser keeps refreshing the session as long as >it is alive... anybody?). Currently the framework doesn't provide much help with this, and I'm looking at addressing this, but one approach to do what you want might be to subclass application class and overload the load_session method along these lines: class Application(SimpleSessionApp): def load_session(self, ctx): try: SimpleSessionApp.load_session(self, ctx) except SessionExpired: ctx.set_page('timeout') -- Andrew McNamara, Senior Developer, Object Craft http://www.object-craft.com.au/ From andrewm at object-craft.com.au Wed Feb 2 12:40:52 2011 From: andrewm at object-craft.com.au (Andrew McNamara) Date: Wed, 02 Feb 2011 12:40:52 +1100 Subject: [albatross-users] Change to handling of expired sessions Message-ID: <20110202014052.944B720588@longblack.object-craft.com.au> Handling of expired sessions has been a common source of grief. Partly this was because Albatross didn't make it easy to do something with the SessionExpired exception, but also an incompatibility between the default exception handling and the cookie based sessions resulted in a SessionExpired loop. To address this, I have changed the Application.run() method to catch SessionExpired exceptions, and call a new application handle_session_expired() method. The new method renders a session_expired.html template if you supply it, falling back to a "your session has expired" HTML message if not. This also addresses the SessionExpired loop problem as a side effect. The SessionExpired loop problem occurred because the SessionFileContextMixin and SessionServerContextMixin relied on being able to clear the session cookie, but this only happens if the response to the browser occurs via the application context; the handle_exception() method either uses a fresh context, or responds directly via the request object, so the cookie was never cleared. This change will appear in the next Albatross release (no due date, yet), so if you have any comments or suggestions, let me know. You may also need to change your expired session handling in existing applications when you adopt this version, although methods that relied on overloading load_session() and catching the SessionExpired exception there will continue to work. Index: albatross/app.py =================================================================== --- albatross/app.py (revision 16709) +++ albatross/app.py (revision 16710) @@ -13,6 +13,7 @@ from albatross.context import NamespaceMixin, ExecuteMixin, ResourceMixin,\ _caller_globals +from albatross.template import Template from albatross import tags from albatross.common import * @@ -283,6 +284,8 @@ except Redirect, e: self.save_session(ctx) ctx.send_redirect(e.loc) + except SessionExpired: + self.handle_session_expired(ctx, req) except: self.handle_exception(ctx, req) return req.return_code() @@ -305,6 +308,24 @@ del etype, value, tb return pyexc, htmlexc + def handle_session_expired(self, ctx, req): + try: + templ = self.load_template('session_expired.html') + except TemplateLoadError: + templ = Template(ctx, '', +''' + + + Session has expired + + +

Your session has expired

+ + +''') + templ.to_html(ctx) + ctx.flush_content() + def handle_exception(self, ctx, req): pyexc, htmlexc = self.format_exception() req.set_status(HTTP_INTERNAL_SERVER_ERROR) -- Andrew McNamara, Senior Developer, Object Craft http://www.object-craft.com.au/