-
-
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__="[email protected]" | |
| __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'