From ec71d8b7dd0569ffefbaa066d1fe0a81cf5c0fec Mon Sep 17 00:00:00 2001 From: Maximillian Dornseif Date: Mon, 7 May 2012 10:48:40 +0300 Subject: [PATCH 1/3] With AppEngine/python27 I see occasional problems that there is no session initialized. This patch makes the error 1) non-fatal and 2) debugging much easier. --- gaesessions/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gaesessions/__init__.py b/gaesessions/__init__.py index 01caf6f..5e01e83 100644 --- a/gaesessions/__init__.py +++ b/gaesessions/__init__.py @@ -35,6 +35,9 @@ def get_current_session(): """Returns the session associated with the current request.""" + if not hasattr(_tls, 'current_session'): + logging.warn('no current Session for this thread - your setup might be broken') + set_current_session(Session()) return _tls.current_session From f276efa810342c91c37f01a3424777acb953a533 Mon Sep 17 00:00:00 2001 From: Maximillian Dornseif Date: Tue, 24 Sep 2013 09:15:13 +0200 Subject: [PATCH 2/3] Revert "With AppEngine/python27 I see occasional problems that there is no session initialized. This patch makes the error 1) non-fatal and 2) debugging much easier." This reverts commit ec71d8b7dd0569ffefbaa066d1fe0a81cf5c0fec. --- gaesessions/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/gaesessions/__init__.py b/gaesessions/__init__.py index 5e01e83..01caf6f 100644 --- a/gaesessions/__init__.py +++ b/gaesessions/__init__.py @@ -35,9 +35,6 @@ def get_current_session(): """Returns the session associated with the current request.""" - if not hasattr(_tls, 'current_session'): - logging.warn('no current Session for this thread - your setup might be broken') - set_current_session(Session()) return _tls.current_session From b93b400909a13b1729e1ec83301b17025015f0d1 Mon Sep 17 00:00:00 2001 From: Maximillian Dornseif Date: Tue, 24 Sep 2013 09:55:00 +0200 Subject: [PATCH 3/3] Allow certain paths to be excluded from session generation. `Set-Cookie` headers do not interact well with many HTTP-caching strategies. This changesets makes it easy to avoid generating session Information and thus `Set-Cookie` headers for certain paths. E.g. app = SessionMiddleware(app, cookie_key=COOKIE_KEY, ignore_paths='^/images/.*$') --- README.markdown | 10 ++++++++-- gaesessions/__init__.py | 17 ++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/README.markdown b/README.markdown index e870f77..07096f8 100644 --- a/README.markdown +++ b/README.markdown @@ -87,6 +87,12 @@ session data might be lost at any time! If cookie-only sessions have not been disabled, then small sessions will still be stored in cookies (this is faster than memcache). +If you want to exclude certain URLs from session generation use the `ignore_paths` +parameter, which should contain a regular expression. Not generating Cookies is +important in certian HTTP caching scenarios. E.g. + + app = SessionMiddleware(app, cookie_key=COOKIE_KEY, ignore_paths='^/images/.*$') + You will also want to create a cronjob to periodically remove expired sessions from the datastore. You can find the [example cronjob](http://github.com/dound/gae-sessions/tree/master/demo/cron.yaml) and @@ -135,8 +141,8 @@ for authentication Here's a few lines of example code too: session.regenerate_id() -_Author_: [David Underhill](http://www.dound.com) -_Updated_: 2011-Jul-03 (v1.07) +_Author_: [David Underhill](http://www.dound.com) +_Updated_: 2011-Jul-03 (v1.07) _License_: Apache License Version 2.0 For more information, please visit the [gae-sessions webpage](http://wiki.github.com/dound/gae-sessions/). diff --git a/gaesessions/__init__.py b/gaesessions/__init__.py index 01caf6f..9d03095 100644 --- a/gaesessions/__init__.py +++ b/gaesessions/__init__.py @@ -7,6 +7,7 @@ import logging import pickle import os +import re import threading import time @@ -446,16 +447,19 @@ class SessionMiddleware(object): memcache/datastore latency which is critical for small sessions. Larger sessions are kept in memcache+datastore instead. Defaults to 10KB. """ - def __init__(self, app, cookie_key, lifetime=DEFAULT_LIFETIME, no_datastore=False, cookie_only_threshold=DEFAULT_COOKIE_ONLY_THRESH): + def __init__(self, app, cookie_key, lifetime=DEFAULT_LIFETIME, no_datastore=False, cookie_only_threshold=DEFAULT_COOKIE_ONLY_THRESH, ignore_paths=None): self.app = app self.lifetime = lifetime self.no_datastore = no_datastore self.cookie_only_thresh = cookie_only_threshold self.cookie_key = cookie_key + self.ignore_paths = ignore_paths if not self.cookie_key: raise ValueError("cookie_key MUST be specified") if len(self.cookie_key) < 32: raise ValueError("RFC2104 recommends you use at least a 32 character key. Try os.urandom(64) to make a key.") + if self.ignore_paths: + self.ignore_paths = re.compile(self.ignore_paths) def __call__(self, environ, start_response): # initialize a session for the current user @@ -463,10 +467,13 @@ def __call__(self, environ, start_response): # create a hook for us to insert a cookie into the response headers def my_start_response(status, headers, exc_info=None): - _tls.current_session.save() # store the session if it was changed - for ch in _tls.current_session.make_cookie_headers(): - headers.append(('Set-Cookie', ch)) - return start_response(status, headers, exc_info) + if self.ignore_paths and self.ignore_paths.match(environ['PATH_INFO']): + return start_response(status, headers, exc_info) + else: + _tls.current_session.save() # store the session if it was changed + for ch in _tls.current_session.make_cookie_headers(): + headers.append(('Set-Cookie', ch)) + return start_response(status, headers, exc_info) # let the app do its thing return self.app(environ, my_start_response)