3

我有许多 tornado.web.RequestHandler 类,它们使用 id 和访问密钥安全 cookie 测试授权访问。我使用 gen.Task 通过内联回调异步访问 mongodb。由于它的异步性,我无法找出一种方法来分解重复代码。我怎样才能做到这一点?

class MyHandler(RequestHandler):
    @tornado.web.asynchronous
    @gen.engine
    def get(self):
        id = self.get_secure_cookie('id', None)
        accesskey = self.get_secure_cookie('accesskey', None)
        if not id or not accesskey:
            self.redirect('/a_public_area')
            return
        try:
            # convert to bson id format to access mongodb
            bson.objectid.ObjectId(id)
        except:
            # if not valid object id
            self.redirect('/a_public_area')
            return
        found_id, error = yield gen.Task(asyncmong_client_inst.collection.find_one, 
            {'_id': id, 'accesskey': accesskey}, fields={'_id': 1})
        if error['error']:
            raise HTTPError(500)
            return
        if not found_id[0]:
            self.redirect('/a_public_area')
            return

        # real business code follows

我想将上述因素纳入一个可能产生 HTTP 状态代码的函数中。

4

3 回答 3

3

也许是装饰者(未经测试或其他任何东西,只是一些想法)

def sanitize(fn):
    def _sanitize(self, *args, **kwargs):
        id = self.get_secure_cookie('id', None)
        accesskey = self.get_secure_cookie('accesskey', None)
        if not id or not accesskey:
            self.redirect('/a_public_area')
            return
        try:
            # convert to bson id format to access mongodb
            bson.objectid.ObjectId(id)
        except:
            # if not valid object id
            self.redirect('/a_public_area')
            return
        return fn(self, *args, **kwargs)
    return _sanitize

不知道您是否可以使 check_errors 与业务逻辑一起使用..但也许..

def check_errors(fn):
   def _check_errors(*args, **kwargs)
      found_id, error = fn(*args, **kwargs)
      if error['error']:
         raise HTTPError(500)
         return
      if not found_id[0]:
         self.redirect('/a_public_area')
         return
   return _check_errors

然后

class MyHandler(RequestHandler):
    @tornado.web.asynchronous
    @gen.engine
    @sanitize
    @check_errors #..O.o decorators
    def get(self):
        found_id, error = yield gen.Task(asyncmong_client_inst.collection.find_one, 
              {'_id': id, 'accesskey': accesskey}, fields={'_id': 1})
        return found_id, error
于 2012-07-26T06:12:00.390 回答
3

Tornado 有装饰器@tornado.web.authenticated。让我们使用它。

class BaseHandler(RequestHandler):
   def get_login_url(self):
        return u"/a_public_area"

    @gen.engine #Not sure about this step
    def get_current_user(self):
        id = self.get_secure_cookie('id', None)
        accesskey = self.get_secure_cookie('accesskey', None)
        if not id or not accesskey:
            return False

        #Are you sure need this? 
        try:
            # convert to bson id format to access mongodb
            bson.objectid.ObjectId(id)
        except:
            # if not valid object id
            return False

        #I believe that you don't need asynchronous mongo on auth query, so if it's not working - replace it with sync call
        found_id, error = yield gen.Task(asyncmong_client_inst.collection.find_one, 
            {'_id': id, 'accesskey': accesskey}, fields={'_id': 1})

        if error['error']:
            raise HTTPError(500)

        if not found_id[0]:
            return False

        return found_id


class MyHandler(BaseHandler):
    @tornado.web.asynchronous
    @tornado.web.authenticated
    @gen.engine
    def get(self):
        # real business code follows

到处使用gen- 不是好的做法。它可以把这个世界变成大意大利面。想想看。

于 2012-07-26T08:52:13.557 回答
2

我想用 gen.Task 解决这个普遍的问题,即分解出代码要么是不可能的,要么是极其笨拙的。

您只能在 get() 或 post() 方法中执行“yield gen.Task(...)”。如果你想让 get() 调用另一个函数 foo(),并在 foo() 中完成工作,那么:你不能,除非你想把所有东西都写成一个生成器,并以某种笨拙的方式将它们链接在一起。随着您的项目变得越来越大,这将是一个大问题。

这是一个更好的选择:https ://github.com/mopub/greenlet-tornado

我们使用它将大型同步代码库转换为 Tornado,几乎没有任何更改。

于 2012-11-21T23:04:41.503 回答