Added SESSION_REFRESH_EACH_REQUEST config option.

This also changes how sessions are being refreshed.  With the new
behavior set-cookie is only emitted if the session is modified or if the
session is permanent.  Permanent sessions can be set to not refresh
automatically through the SESSION_REFRESH_EACH_REQUEST config key.

This fixes #798.
This commit is contained in:
Armin Ronacher 2013-07-30 16:43:54 +02:00
parent 1a66a7e110
commit d1d835c023
5 changed files with 96 additions and 0 deletions

View file

@ -285,6 +285,7 @@ class Flask(_PackageBoundObject):
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'SESSION_REFRESH_EACH_REQUEST': True,
'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': 12 * 60 * 60, # 12 hours
'TRAP_BAD_REQUEST_ERRORS': False,

View file

@ -252,6 +252,24 @@ class SessionInterface(object):
if session.permanent:
return datetime.utcnow() + app.permanent_session_lifetime
def should_set_cookie(self, app, session):
"""Indicates weather a cookie should be set now or not. This is
used by session backends to figure out if they should emit a
set-cookie header or not. The default behavior is controlled by
the ``SESSION_REFRESH_EACH_REQUEST`` config variable. If
it's set to `False` then a cookie is only set if the session is
modified, if set to `True` it's always set if the session is
permanent.
This check is usually skipped if sessions get deleted.
.. versionadded:: 1.0
"""
if session.modified:
return True
save_each = app.config['SESSION_REFRESH_EACH_REQUEST']
return save_each and session.permanent
def open_session(self, app, request):
"""This method has to be implemented and must either return `None`
in case the loading failed because of a configuration error or an
@ -315,11 +333,26 @@ class SecureCookieSessionInterface(SessionInterface):
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
# Delete case. If there is no session we bail early.
# If the session was modified to be empty we remove the
# whole cookie.
if not session:
if session.modified:
response.delete_cookie(app.session_cookie_name,
domain=domain, path=path)
return
# Modification case. There are upsides and downsides to
# emitting a set-cookie header each request. The behavior
# is controlled by the :meth:`should_set_cookie` method
# which performs a quick check to figure out if the cookie
# should be set or not. This is controlled by the
# SESSION_REFRESH_EACH_REQUEST config flag as well as
# the permanent flag on the session itself.
if not self.should_set_cookie(app, session):
return
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
expires = self.get_expiration_time(app, session)

View file

@ -345,6 +345,49 @@ class BasicFunctionalityTestCase(FlaskTestCase):
self.assert_equal(type(rv['b']), bytes)
self.assert_equal(rv['t'], (1, 2, 3))
def test_session_cookie_setting(self):
app = flask.Flask(__name__)
app.testing = True
app.secret_key = 'dev key'
is_permanent = True
@app.route('/bump')
def bump():
rv = flask.session['foo'] = flask.session.get('foo', 0) + 1
flask.session.permanent = is_permanent
return str(rv)
@app.route('/read')
def read():
return str(flask.session.get('foo', 0))
def run_test(expect_header):
with app.test_client() as c:
self.assert_equal(c.get('/bump').data, '1')
self.assert_equal(c.get('/bump').data, '2')
self.assert_equal(c.get('/bump').data, '3')
rv = c.get('/read')
set_cookie = rv.headers.get('set-cookie')
self.assert_equal(set_cookie is not None, expect_header)
self.assert_equal(rv.data, '3')
is_permanent = True
app.config['SESSION_REFRESH_EACH_REQUEST'] = True
run_test(expect_header=True)
is_permanent = True
app.config['SESSION_REFRESH_EACH_REQUEST'] = False
run_test(expect_header=False)
is_permanent = False
app.config['SESSION_REFRESH_EACH_REQUEST'] = True
run_test(expect_header=False)
is_permanent = False
app.config['SESSION_REFRESH_EACH_REQUEST'] = False
run_test(expect_header=False)
def test_flashes(self):
app = flask.Flask(__name__)
app.secret_key = 'testkey'