Вопрос

I'm trying to get my head around Tornado. I'm writing a chat application backed by mongodb and I'm using motor for non-blocking access to it.

What I'm trying to achieve is:

  1. Create a decorator that uses motor to asynchronously pull the user's record from mongo
  2. Validate their credentials (username & token)
  3. Create another decorator that checks that the user_id retrieved in 1. above is permitted access to the chat room. This requires another asynchronous call to mongo with motor to retrieve the 'ChatRoom' record.
  4. Subscribe to the chat room if all is OK

I have decorator 1. working (basically taken from http://tornadogists.org/5251927/):

def authenticated_async(f):
    @functools.wraps(f)
    @gen.engine
    def wrapper(self, *args, **kwargs):
        self.current_user = yield gen.Task(self.get_current_user_async)
        if self.current_user:
            logging.info("User '%s' (%s) successfully authenticated" %
                         (self.current_user['username'],
                          self.current_user['_id']))
            f(self, *args, **kwargs)
        else:
            raise tornado.web.HTTPError(401, "User not authenticated, "
                                             "aborting")
    return wrapper

The trouble is that for the second decorator, I need to access self.current_user. Because this is set in an asynchronous callback, it's not available when I get into my validation decorator (i.e the validation decorator is called before the auth decorator completes).

Is it just not possible for me to use decorators in this way with asynchronous functions? Do I just need to call the validation method inside the above method after making sure that self.current_user is True so it's more like a callback?

I'd ideally like to have my methods in my Handler wrapped with both of these decorators so I can reuse them elsewhere, i.e.:

class ChatSocketHandler(tornado.websocket.WebSocketHandler):
    @gen.coroutine
    @validate_invitation_access_async
    @authenticated_async
    def open(self, invitation_id):
        # do stuff here...

Update In fact, there is no dependency. user_id is provided as a parameter, and that could be used to run both decorators in parallel - one to confirm authentication, the other to see whether the user with that ID is allowed access to the room. The open() method would then only proceed if self.auth_check == True and self.room_check == True.

Could open() ever be called before the async decorators complete though?

Это было полезно?

Решение

You need to switch the order of the decorators so your validate_invitation_access_async wrapper has access to current_user:

@authenticated_async
@validate_invitation_access_async
def open(self, invitation_id):
        # do stuff here...

Then the wrapper inside validate_invitation_access_async is the "f" in authenticated_async: it's called after self.current_user is set. Note that you don't need an additional gen.engine decorator, all the wrappers are already engines. Your wrapper could be like:

def validate_invitation_access_async(f):
    @gen.engine
    def wrapper(self, *args, **kwargs):
        # ... use Motor to see if user is authorized ...
        # if not, log and redirect. otherwise:
        f(self, *args, **kwargs)

You should also update your code to use Tornado 3's gen.coroutine instead of gen.engine: it's much cleaner. But I leave that as an exercise.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top