Webapp2 sessions are lost between requests - python

I'm using webapp2_extras to set a session variable. If I print the variable after setting it the value is correct. The save_sessions call is also made. However, when I make the request again the variable isn't set.
Most of the code is taken from the webapp2 documentation:
import webapp2
from webapp2_extras import sessions
class BaseHandler(webapp2.RequestHandler):
def dispatch(self):
self.session_store = sessions.get_store(request=self.request)
try:
webapp2.RequestHandler.dispatch(self)
finally:
self.session_store.save_sessions(self.response)
#webapp2.cached_property
def session(self):
return self.session_store.get_session()
class MainHandler(BaseHandler):
def get(self):
foo = self.session.get('foo')
print foo
self.session['foo'] = 'bar'
foo = self.session.get('foo')
print foo
self.response.write(BaseHandler)
config = {}
config["webapp2_extras.sessions"] = {
"secret_key": "key",
}
app = webapp2.WSGIApplication([
('/', MainHandler)
], debug=True, config=config)
This always returns "None something" but I want it to return "something something" for the second request.

I know this is a fairly old question but I just ran into the same problem and here is what solved it for me.
I altered the webapp2 config object to add in the auth param.
What used to be:
config = {}
config["webapp2_extras.sessions"] = {"secret_key": "key" }
app = webapp2.WSGIApplication([('/', MainHandler)], debug=True, config=config)
Should now be:
config = {}
config["webapp2_extras.sessions"] = {"secret_key": "key" }
config["webapp2_extras.auth"] = {'session_backend': 'securecookie'}
app = webapp2.WSGIApplication([('/', MainHandler)], debug=True, config=config)

Looking at my config I have this to set the location of the session store (for app engine the config is a bit different to the standard I understand)
#webapp2.cached_property
def session(self):
# Returns a session using the default cookie key.
return self.session_store.get_session(name='mc_session',
factory=sessions_memcache.MemcacheSessionFactory)
Try that perhaps? Otherwise your code look OK to me, without actually running it.
You'll also need this import:
from webapp2_extras import sessions_memcache
Webapp2 Memcache sessions

Assuming you are using the datastore as the session backend, this probably has to do with eventual consistency. If you try to postpone the second request for a few seconds, and then attempt the request you will probably see the session data, and that proves it.
Switching to securecookie may help, though it didn't work for me for some reason... Still looking into it.
A big part of my problem was actually due to my ignorance of the cookie standards. It is explained in this other stackoverflow question.

I had a similar issue. What fixed it for me was to set the backend to be different from the default (securecookie). setting it to memcache seemed to work for me.
#webapp2.cached_property
def session(self):
return self.session_store.get_session(backend="memcache")
I also set this in the config under .auth. Not sure if this overwrites the backend parameter for get_session anyway...
config["webapp2_extras.auth"] = {'session_backend': 'memcache'}

Related

Why is id of object returned by session._get_current_object() is changed on every request, while id of session remains unchanged?

I am using:
Python 3.6.1
Flask 0.12.2
Section on session of Flask documentation says that:
This is a proxy.
and section on proxies elaborates that:
Some of the objects provided by Flask are proxies to other objects.
The reason behind this is that these proxies are shared between
threads and they have to dispatch to the actual object bound to a
thread behind the scenes as necessary. ...
If you need to get access
to the underlying object that is proxied, you can use the
_get_current_object() method
This all is pretty much straightforward.
But when I try the following:
from flask import (
Flask,
session,
)
app = Flask(__name__)
app.secret_key = 'some random secret key'
#app.route('/')
def index():
print("session ID is: {}".format(id(session)))
print("session._get_current_object() ID is: {}".format(id(session._get_current_object())))
print('________________________________')
return 'Check the console! ;-)'
each time I make a request to / — the value of id(session._get_current_object()) is different, while id(session) remains the same.
Following Flask documentation, quoted above, it should be the other way around. So why is this happening?
UPDATE
inspired by brunns's suggestion in the comments to his answer, that there is one underlying object per thread
Here is some code, to test assumption that there is one underlying session object (session._get_current_object()) per thread:
import threading
from flask import (
Flask,
session,
)
app = Flask(__name__)
app.secret_key = 'some random secret key'
#app.route('/')
def index():
print("session ID is: {}".format(id(session)))
print("session._get_current_object() ID is: {}".format(id(session._get_current_object())))
print("threading.current_thread().ident is: {}".format(threading.current_thread().ident))
print('________________________________')
return 'Check the console! ;-)'
Despite the expectations, threading.current_thread().ident) is never changed, while is id(session._get_current_object() is changing.
session is an object you have imported from the flask module. You only import it once, and it doesn't change, so nor will its id(). It's shared between threads, and it's a proxy to the underlying objects.
Each request may be run on a different thread, and each will have a different underlying object, so they may have different id()s.

Difficulty implementing server side session storage using redis and flask

I have a setup where a node.js app is making ajax requests to a flask based python server. Since ajax requests lack cookie data, I can't use the simple flask session object to persist data across requests. To remedy this, I'd like to implement a redis based server side implementation of a session storage system, but the solutions I've found so far do not work.
One solution I've tried is the following this snippet.
But this doesn't work. Is there more setup I need to do to configure redis beyond what is mentioned in the quickstart guide? Here is my attempt:
...
from flask import session
# Snippet code is copy pasted here verbatum
import session_interface
...
app = Flask(__name__)
app.session_interface = session_interface.RedisSessionInterface()
...
# Can't access this as session['key'] across requests
session['key'] = value
...
if __name__ == '__main__':
app.secret_key = '123456789012345678901234'
app.run(debug=True)
Another solution I've tried is importing the Flask-Session extention.
However, I can't get this to work either. The section I'm confused about is the following:
"We are not supplying something like SESSION_REDIS_HOST and SESSION_REDIS_PORT, if you want to use the RedisSessionInterface, you should configure SESSION_REDIS to your own redis.Redis instance. This gives you more flexibility, like maybe you want to use the same redis.Redis instance for cache purpose too, then you do not need to keep two redis.Redis instance in the same process."
What is meant by this section and how would I have figured this out? Here is my attempt to make this extension work:
...
from flask import session
from flask_session import Session
import redis
...
app = Flask(__name__)
SESSION_TYPE = 'redis'
app.config.from_object(__name__)
Session(app)
...
# Can't access this as session['key'] across requests
session['key'] = value
...
if __name__ == '__main__':
app.secret_key = '123456789012345678901234'
app.run(debug=True)
Has anyone successfully implemented manual session storage on a server running flask? Are there other options for getting this functionality?
Thanks for your input.
I think that's because you missed the URL configuration for your storage Redis, to check that, you can use Redis-CLI to see if there is anything being inserted into Redis.
I use this code and it worked:
from flask import Flask
from flask_session import Session
import redis
……
app = Flask(__name__)
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.from_url('127.0.0.1:6379')
sess = Session()
sess.init_app(app)
def getSession():
return session.get('key', 'not set')
def setSession():
session.set('key')=123
return 'ok'
……
The following works for me:
...
from flask_session import Session
import redis
...
app = Flask(__name__)
SECRET_KEY = '123456789012345678901234'
SESSION_TYPE = 'redis'
SESSION_REDIS = redis.from_url('localhost:6379')
app.config.from_object(__name__)
sess = Session()
sess.init_app(app)
...
# Should be available accross requests now
session['key'] = value
...
if __name__ == '__main__':
app.run(debug=True)
Using sess.init_app(app) instead of Session(app) did the trick.

Set up multiple session handlers on python webapp2

I'm writing a simple web application in google appengine and python. In this application I need to handle two types of sessions:
the "long term session" that stores information about users, current page ecc, with long max_age parameter and the "short term session" with max_age about 20 minutes that keep an access token for the autentication via API.
I have implemented the following BaseHandler:
import webapp2
from webapp2_extras import sessions
class BaseHandler(webapp2.RequestHandler):
def dispatch(self):
# Get a session store for this request.
self.session_store = sessions.get_store(request=self.request)
try:
# Dispatch the request.
webapp2.RequestHandler.dispatch(self)
finally:
# Save all sessions.
self.session_store.save_sessions(self.response)
#webapp2.cached_property
def session(self):
# Returns a session using the default cookie key.
return self.session_store.get_session(backend='memcache')
#webapp2.cached_property
def session_auth(self):
return self.session_store.get_session(backend='memcache', max_age=20*60)<code>
the problem is that all sessions have max_age=20*60 seconds (and not only the sessions accessible by self.session_auth)..
How should I solve this?
thanks
Try setting your config params:
config = {}
config['webapp2_extras.sessions'] = {
'session_max_age': 100000, # try None here also, though that is the default
}
app = webapp2.WSGIApplication([
('/', HomeHandler),
], debug=True, config=config)

Using pytest with gaesessions session middleware in appengine

When I run py.test --with-gae, I get the following error (I have pytest_gae plugin installed):
def get_current_session():
"""Returns the session associated with the current request."""
> return _tls.current_session
E AttributeError: 'thread._local' object has no attribute 'current_session'
gaesessions/__init__.py:50: AttributeError
I'm using pytest to test my google appengine application. The application runs fine when run in the localhost SDK or when deployed to GAE servers. I just can't figure out how to make pytest work with gaesessions.
My code is below:
test_handlers.py
from webtest import TestApp
import appengine_config
def pytest_funcarg__anon_user(request):
from main import app
app = appengine_config.webapp_add_wsgi_middleware(app)
return TestApp(app)
def test_session(anon_user):
from gaesessions import get_current_session
assert get_current_session()
appengine_config.py
from gaesessions import SessionMiddleware
def webapp_add_wsgi_middleware(app):
from google.appengine.ext.appstats import recording
app = recording.appstats_wsgi_middleware(app)
app = SessionMiddleware(app, cookie_key="replaced-with-this-boring-text")
return app
Relevant code from gaesessions:
# ... more code are not show here ...
_tls = threading.local()
def get_current_session():
"""Returns the session associated with the current request."""
return _tls.current_session
# ... more code are not show here ...
class SessionMiddleware(object):
"""WSGI middleware that adds session support.
``cookie_key`` - A key used to secure cookies so users cannot modify their
content. Keys should be at least 32 bytes (RFC2104). Tip: generate your
key using ``os.urandom(64)`` but do this OFFLINE and copy/paste the output
into a string which you pass in as ``cookie_key``. If you use ``os.urandom()``
to dynamically generate your key at runtime then any existing sessions will
become junk every time your app starts up!
``lifetime`` - ``datetime.timedelta`` that specifies how long a session may last. Defaults to 7 days.
``no_datastore`` - By default all writes also go to the datastore in case
memcache is lost. Set to True to never use the datastore. This improves
write performance but sessions may be occassionally lost.
``cookie_only_threshold`` - A size in bytes. If session data is less than this
threshold, then session data is kept only in a secure cookie. This avoids
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):
self.app = app
self.lifetime = lifetime
self.no_datastore = no_datastore
self.cookie_only_thresh = cookie_only_threshold
self.cookie_key = cookie_key
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.")
def __call__(self, environ, start_response):
# initialize a session for the current user
_tls.current_session = Session(lifetime=self.lifetime, no_datastore=self.no_datastore, cookie_only_threshold=self.cookie_only_thresh, cookie_key=self.cookie_key)
# 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)
# let the app do its thing
return self.app(environ, my_start_response)
The problem is that your gae sessions is not yet called until the app is also called. The app is only called when you make a request to it. Try inserting a request call before you check for the session value. Check out the revised test_handlers.py code below.
def test_session(anon_user):
anon_user.get("/") # get any url to call the app to create a session.
from gaesessions import get_current_session
assert get_current_session()

Creating a dynamic redirect by using any of Flask, Pyramid, or Bottle?

I want to create a webapp that dynamically redirects to a URL, based on address that user typed. When a user visit my site by a address like this:
http://mydomain1.com/a1b2c3d4
I want redirect this user to URL:
http://mydomain2.com/register.php?id=a1b2c3d4&from=mydomain1.com
Yay, I love a good fight!
from pyramid.config import Configurator
from pyramid.httpexceptions import HTTPFound
from paste.httpserver import serve
config = Configurator()
config.add_route('redirect', '/{arg}')
def redirect_view(request):
dst = 'http://mydomain2.com/register.php?id={id}&from={host}'
args = {
'id': request.matchdict['arg'],
'host': request.host,
}
return HTTPFound(dst.format(**args))
config.add_view(redirect_view, route_name='redirect')
serve(config.make_wsgi_app(), host='0.0.0.0', port=80)
Here goes my attempt, I'm almost newbie in flask, so it should have room to improve
from flask import Flask, redirect, request
app = Flask(__name__)
host = 'domain2.org'
#app.route('/<path>')
def redirection(path):
return redirect('http://'+host+'/register.php?id='+path+'&from='+request.host)
if __name__ == '__main__':
app.run()
Edited to add the host to the from parameter
My solution was to use a Werkzeug rule using the path type :
host = 'domain2.org'
#app.route('/<path:path>')
def redirection(path):
return redirect('http://%s/%s' % (host, path), code=301)
This can be useful if you move a site and want another site instead with redirection on others pages.
There's a pyramid_rewrite extension (https://pypi.python.org/pypi/pyramid_rewrite/) that looks unmaintained, but seems to work. I had a use case it didn't handle, though: using Configure.include() with the route_prefix parameter.
It occurred to me that the usual approach is to do URL rewrites in the server, and I was using a WSGI server from the Python standard library. How hard could it be?
Make a custom request handler class:
from wsgiref.simple_server import make_server, WSGIRequestHandler
class MyReqHandler(WSGIRequestHandler):
def get_environ(self):
env = WSGIRequestHandler.get_environ(self)
if env['PATH_INFO'].startswith('/foo'):
env['PATH_INFO'] = env['PATH_INFO'].replace('foo', 'bar', 1)
return env
Pass it to make_server() when creating your server:
srvr = make_server('0.0.0.0', 6543, app, handler_class=MyReqHandler)
It works!
Straight-up substitution is all I needed for the problem at hand. Extending it to use regular expressions and exposing it via a nice API would be pretty straightforward.
I have another solution, that is straight-up pyramid, so it will work with some other wsgi server:
from pyramid.events import NewRequest, subscriber
#subscriber(NewRequest)
def mysubscriber(event):
req = event.request
if req.path_info.startswith('/~cfuller'):
req.path_info = req.path_info.replace('foo', 'bar', 1)
That's the declarative way, and it requires a config.scan(). Imperitively, you'd do something like
config.add_subscriber(mysubscriber, NewRequest)
See http://docs.pylonsproject.org/projects/pyramid/en/1.5-branch/narr/events.html for the skinny on events.

Categories