-
-
Save joerussbowman/7686878 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python | |
# | |
# Copyright 2013 Joseph Bowman | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); you may | |
# not use this file except in compliance with the License. You may obtain | |
# a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
# License for the specific language governing permissions and limitations | |
# under the License. | |
import motor | |
import functools | |
import uuid | |
import datetime | |
import settings | |
import time | |
import logging | |
import bson | |
from tornado.web import RequestHandler | |
class MoSession(object): [174/207] | |
""" | |
MoSession class, used to manage persistence across multiple requests. Uses | |
a mongodb backend and Cookies. This library is designed for use with | |
Tornado. | |
Built on top of Motor. | |
The decorator is written to be completely asynchronous and not block. | |
The session is added as session property to your request handler, ie: | |
self.session. It can be manipulated as your would any dictionary object. | |
Included with the library is a settings file, configured for default | |
permissions. Some of the more advanced tuning you can do is with token | |
expiration. In order to create some additional security for sessions used | |
in a non-ssl environment, the token stored in the browser rotates. If you | |
are using ssl, or more interested in performance than security you can set | |
SESSION_TOKEN_TTL to an extremely high number to avoid writes. | |
Note: In an effort increate performance, all data writes are delayed until | |
after the request method has completed. However, security token updates | |
are saved as they happen. | |
""" | |
def __init__(self, req_obj, | |
cookie_path=settings.session["DEFAULT_COOKIE_PATH"], | |
cookie_name=settings.session["COOKIE_NAME"], | |
set_cookie_expires=settings.session["SET_COOKIE_EXPIRES"], | |
session_token_ttl=settings.session["SESSION_TOKEN_TTL"], | |
session_expire_time=settings.session["SESSION_EXPIRE_TIME"], | |
mongo_collection=settings.session["MONGO_COLLECTION"], | |
db=None, | |
callback=None): [142/207] | |
""" | |
__init__ loads the session, checking the browser for a valid session | |
token. It will either validate the session and/or create a new one | |
if necessary. | |
The db object should be a mongodb database, not collection. The | |
collection value is set by the settings for the library. See | |
settings.py for more information. | |
If you already have a db attribute on the request or application | |
objects then there is no need to pass it. Sessions will automatically | |
check those objects for a valid database object to use. | |
""" | |
logging.error("starting init") | |
self.req_obj = req_obj | |
self.cookie_path = cookie_path | |
self.cookie_name = cookie_name | |
self.session_token_ttl = session_token_ttl | |
self.session_expire_time = session_expire_time | |
self.callback = callback | |
if db: | |
self.db = db[mongo_collection] | |
elif hasattr(self.req_obj, "db"): | |
self.db = self.req_obj.db[mongo_collection] | |
elif hasattr(self.req_obj.application, "db"): | |
self.db = self.req_obj.application.db[mongo_collection] | |
else: | |
raise ValueError("Invalid value for db") | |
self.new_session = True | |
self.do_put = False | |
self.do_save = False [110/207] | |
self.do_delete = False | |
self.cookie = self.req_obj.get_secure_cookie(cookie_name) | |
if self.cookie: | |
logging.error("got cookie %s" % self.cookie) | |
(self.token, _id) = self.cookie.split("@") | |
logging.error("looking up session") | |
self.session = self.db.find_one({"_id": | |
bson.ObjectId(_id)}, callback=self._validate_cookie) | |
else: | |
logging.error("no cookie") | |
self._new_session() | |
def _new_session(self): | |
logging.error("starting new session") | |
self.session = {"_id": bson.ObjectId(), | |
"tokens": [str(uuid.uuid4())], | |
"last_token_update": datetime.datetime.utcnow(), | |
"data": {}, | |
} | |
self._put() | |
def _validate_cookie(self, response, error): | |
logging.error("validating cookie") | |
if response: | |
self.session = response | |
if self.token in self.session["tokens"]: | |
self.new_session = False | |
if self.new_session: | |
self._new_session() | |
else: | |
duration = datetime.timedelta(seconds=self.session_token_ttl) [78/207] | |
session_age_limit = datetime.datetime.utcnow() - duration | |
if self.session['last_token_update'] < session_age_limit: | |
self.token = str(uuid.uuid4()) | |
if len(self.session['tokens']) > 2: | |
self.session['tokens'].pop(0) | |
self.session['tokens'].insert(0,self.token) | |
self.session["last_token_update"] = datetime.datetime.utcnow() | |
self.do_put = True | |
if self.do_put: | |
self._put() | |
else: | |
self._handle_response() | |
def _put(self): | |
logging.error("storing id") | |
if self.session.get("_id"): | |
self.db.update({"_id": self.session["_id"]}, {"$set": {"data": | |
self.session["data"], "tokens": self.session["tokens"], | |
"last_token_update": self.session["last_token_update"]}}, | |
upsert=True, | |
callback=self._handle_response) | |
else: | |
self.db.save(self.session, callback=self._handle_response) | |
def _handle_response(self, *args, **kwargs): | |
logging.error("setting cookie and running request handler") | |
cookie = "%s@%s" % (self.session["tokens"][0], self.session["_id"]) | |
self.req_obj.set_secure_cookie(name = self.cookie_name, value = | |
cookie, path = self.cookie_path) | |
self.callback(self.req_obj) | |
[46/207] | |
def get_token(self): | |
return self.cookie | |
def get_id(self): | |
return self.session.get("_id") | |
def delete(self): | |
self.session['tokens'] = [] | |
self.do_delete = True | |
return True | |
def has_key(self, keyname): | |
return self.__contains__(keyname) | |
def get(self, key, default=None): | |
if self.has_key(key): | |
return self[key] | |
else: | |
return default | |
def __delitem__(self, key): | |
del self.session["data"][key] | |
self.do_save = True | |
return True | |
def __getitem__(self, key): | |
return self.session["data"][key] | |
def __setitem__(self, key, val): | |
self.session["data"][key] = val | |
self.do_save = True [14/207] | |
return True | |
def __len__(self): | |
return len(self.session["data"]) | |
def __contains__(self, key): | |
return self.session["data"].has_key(key) | |
def __iter__(self): | |
for key in self.session["data"]: | |
yield key | |
def __str__(self): | |
return u"{%s}" % ', '.join(['"%s" = "%s"' % (k, self.session["data"][k]) for k in self.session["data"]]) | |
def _pass(self, response, error): | |
pass | |
def mosession(method): | |
@functools.wraps(method) | |
def wrapper(self, *args, **kwargs): | |
def on_finish(self, *args, **kwargs): | |
""" | |
This is a monkey patch finish which will save or delete | |
session data at the end of a request. | |
""" | |
super(self.__class__, self).on_finish(*args, **kwargs) | |
logging.error("doing finish") | |
if self.session.do_save: | |
db.update({"_id": self.session.session["_id"]}, {"$set": {"data": | |
self.session.session["data"]}}, | |
callback=self.session._pass) | |
if self.session.do_delete: | |
self.session.db.remove({"_id": self.session.session["_id"]}, | |
callback=self.session._pass) | |
logging.error("starting") | |
self.on_finish = functools.partial(on_finish, self) | |
self.session = MoSession(self, callback=method) | |
#method(self, *args, **kwargs) | |
return wrapper |
class DealerCreatedHandler(BaseHandler): | |
@mosession | |
def get(self): | |
logging.error("I am getting run") | |
confirm = self.get_argument("confirm") | |
account = yield motor.Op( | |
db.accounts.find_one, {"confirm": confirm} | |
) | |
if not account: | |
self.render("error.html", error="Could not find account for that code.") | |
else: | |
self.session["account"] = account["_id"] | |
self.render("dealerCreated.html") |
#!/usr/bin/env python | |
# | |
# Copyright 2009 unscatter.com | |
# | |
# This source code is proprietary and owned by jbowman and may not | |
# be copied, distributed, or run without prior permission from the owner. | |
__author__="bowman.joseph@gmail.com" | |
__date__ ="$September 24, 2011 1:50:35 PM$" | |
session = { | |
"COOKIE_NAME": "mosession", | |
"DEFAULT_COOKIE_PATH": "/", | |
"SESSION_EXPIRE_TIME": 7200, # sessions are valid for 7200 seconds | |
# (2 hours) | |
"SET_COOKIE_EXPIRES": True, # Set to True to add expiration field to | |
# cookie | |
"SESSION_TOKEN_TTL": 5, # Number of seconds a session token is valid | |
# for. | |
"UPDATE_LAST_ACTIVITY": 60, # Number of seconds that may pass before | |
# last_activity is updated | |
"MONGO_COLLECTION": 'mosessions', | |
"MONGO_COLLECTION_SIZE": 100000, | |
} |
I updated it, got rid of the gen.coroutine stuff and used callbacks. Was getting a blank page so added a bunch of debugging. The first time I use motor to connect to mongodb it releases everything and runs the request handler. So the set up stuff never happens.
Request looks like
[E 131129 15:56:06 init:224] starting
[E 131129 15:56:06 init:73] starting init
[E 131129 15:56:06 init:96] got cookie b72a364b-b4f7-466f-9ceb-5e6afa727d9e@529685870b49a55ff1272ebf
[E 131129 15:56:06 init:98] looking up session
[I 131129 15:56:06 web:1635] 304 GET /dealer/created/?confirm=8bdbabc5eb7e4a6b87b3281d1e62a3f0 (76.114.245.105) 2.42ms
[E 131129 15:56:06 init:215] doing finish
[E 131129 15:56:06 init:115] validating cookie
[E 131129 15:56:06 init:106] starting new session
[E 131129 15:56:06 init:139] storing id
[E 131129 15:56:06 init:150] setting cookie and running request handler
I need a way to make sure the callback in the session object calls the request object after that's done. I'm not sure why it's skipping ahead.
Path is
mosessions/
init.py
settings.py
The example handler is one that uses the @Mosession decorator...
umm import to get the decorator is
from mosessions import mosession
What's happening when I try to run it is something isn't getting returned right. Maybe I'm missing a yield somewhere?
Traceback (most recent call last):
File "/home/joe/dev/bod/bod-ve/local/lib/python2.7/site-packages/tornado/web.py", line 1144, in _when_complete
if result.result() is not None:
File "/home/joe/dev/bod/bod-ve/local/lib/python2.7/site-packages/tornado/concurrent.py", line 129, in result
raise_exc_info(self.__exc_info)
File "/home/joe/dev/bod/bod-ve/local/lib/python2.7/site-packages/tornado/gen.py", line 221, in wrapper
runner.run()
File "/home/joe/dev/bod/bod-ve/local/lib/python2.7/site-packages/tornado/gen.py", line 507, in run
yielded = self.gen.send(next)
File "/home/joe/dev/bod/mosessions/init.py", line 217, in wrapper
self.session = yield MoSession(self)
TypeError: init() should return None, not 'generator'