异步登录龙卷风

Posted

技术标签:

【中文标题】异步登录龙卷风【英文标题】:Asynchronous login tornado 【发布时间】:2017-07-31 06:29:41 【问题描述】:

我使用 Tornado 创建了一个使用同步方法的登录页面。现在我想让它异步。那么我应该对以下代码做哪些更改:

import tornado.ioloop
import tornado.web
import http
import time

class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):
        return self.get_secure_cookie("user")

class MainHandler(BaseHandler):
    def get(self):
        if not self.current_user:
            self.redirect("/login")
            return
        name = tornado.escape.xhtml_escape(self.current_user)
        self.write('<html>'
    '<head> '
    '<title>WELCOME  </title>'
    '</head>'
    '<body style="background:orange;"'

    '<div align="center">'
    '<div style="margin-top:200px;margin-bottom:10px;">'
    '<span style="width:500px;color:black;font-size:30px;font-weight:bold;">WELCOME </span>'
    '</div>'
    '<div style="margin-bottom:5px;">'"Hello, " + name)

class LoginHandler(BaseHandler):
    def get(self):
        self.write('<html><body><form action="/login" method="post">'
                   '<div><span style="width:100px;;height: 500px;color:black;font-size:60;font-weight:bold;">'
                   'LOGIN'
                   '<div><span style="width:100px;;height: 500px;color:purple;font-size:30;font-weight:bold;">'
                   'NAME <input type="text" name="name">'
                   '<div><span style="width:100px;height: 500px;color:purple;font-size:30;font-weight:bold;">PASSWORD</span><input style="width:150px;" type="password" name="password" id="password" value="">'
                   '</div>'                   
                   '<input type="submit" value="Sign in">'
                   '</form></body></html>')

    def post(self):
        self.set_secure_cookie("user", self.get_argument("name"))
        time.sleep(10)
        self.redirect("/")

application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/login", LoginHandler),
], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")

application.listen(5000)
tornado.ioloop.IOLoop.current().start()

在我的代码中,我有三个类BaseHandlerMainHandlerLoginHandler。任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

不要在 Tornado 方法中调用“睡眠”:

http://www.tornadoweb.org/en/stable/faq.html

如果您想暂停该方法一段时间以向自己证明它是非阻塞的,请添加from tornado import gen 并尝试:

async def post(self):
    self.set_secure_cookie("user", self.get_argument("name"))
    yield gen.sleep(10)
    self.redirect("/")

或者在 Python 2 中:

@gen.coroutine
def post(self):
    self.set_secure_cookie("user", self.get_argument("name"))
    yield gen.sleep(10)
    self.redirect("/")

【讨论】:

我得到以下错误,当我尝试 yield gen.sleep: raise BadYieldError("yielded unknown object %r" % (yielded,)) tornado.gen.BadYieldError: yielded unknown object ERROR:tornado.access:500 POST /login (::1) 8.00ms 有没有办法让这段代码异步。通过改变函数。并使用 '@return_future def future_func(arg1, arg2, callback): # 做事(可能是异步的) callback(result) @gen.engine def caller(callback): yield future_func(arg1, arg2) callback()【参考方案2】:

一个简单的例子

Python 3.5 引入了asyncawait 关键字(使用这些关键字的函数也称为“本机协程”)。详情请见the answer。

我们为 Tornado 应用构建的每个基于类的视图都必须继承自 tornado.web 中的 RequestHandler 对象。

如果我们重写prepare 方法,我们可以设置一些逻辑来运行,只要收到request 就会执行。

def tornado.web.RequestHandler.get_current_user(self) - 覆盖以确定当前用户,例如,cookie。

子类可以覆盖get_current_user(),它会在第一次访问self.current_user 时自动调用。 get_current_user() 每次请求只会被调用一次,并被缓存以备将来访问。

class BaseHandler(tornado.web.RequestHandler):
    def prepare(self):
        if self.get_argument("btn1", None) is not None:
            print("detected click on btn Profile")
            self.redirect("/profile")
            return

        if self.get_argument("btn2", None) is not None:
            print("detected click on btn Sources")
            self.redirect("/sources")
            return

        if self.get_argument("logout", None) is not None:
            self.clear_cookie("username")
            self.redirect("/")
            return

        if self.get_argument("btnSignIn", None) is not None:
            print("detected click on btnSignIn")
            self.redirect("/signin")
            return

    def get_current_user(self):
        a = self.get_secure_cookie("username")
        if a:
            return a.decode("utf-8")
        return None
class LoginHandler(BaseHandler):
    def get(self):
        self.render('login.html')

    def prepare(self):
        super().prepare()

    async def post(self):
        username = self.get_argument("username")
        password = self.get_argument("password")

        my_db = self.settings['db']
        collection = my_db.test

        val = await do_check_one(collection, username, password)

        if val is not None:
            self.set_secure_cookie("username", username)
            self.redirect("/profile")
        else:
            self.write('<h1>Wrong credentials</h1>')

为了更好地理解,请参阅:Simplest async/await example。

async def do_check_one(my_collection, value1, value2):
    document = await my_collection.find_one("name": value1, "password": value2)
    return document

main:

选项必须在使用前用tornado.options.define 定义,通常在模块的顶层。然后这些选项可以作为tornado.options.options 的属性访问。

您的应用程序的main() 方法不需要了解整个程序中使用的所有选项;它们都是在加载模块时自动加载的。但是,所有定义选项的模块都必须在解析命令行之前导入。


define("port", default=8000, help="run on the given port", type=int)


if __name__ == '__main__':
    tornado.options.parse_command_line()

    client = motor.motor_tornado.MotorClient('localhost', 27017)
    db = client.test
    collection = db.test

    settings = 
        "template_path": os.path.join(os.path.dirname(__file__), "templates"),
        "static_path": os.path.join(os.path.dirname(__file__), "static"),
        "cookie_secret": "_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
        "login_url": "/login",
        "db": db,
        "debug": True,
        "xsrf_cookies": True
    

    app = tornado.web.Application(
        handlers=[(r'/', MainHandler),
                  (r'/sources', SourceHandler),
                  (r'/login', LoginHandler),
                  (r'/signin', SigninHandler),
                  (r'/profile', ProfileHandler),
                  (r'/favicon.ico', tornado.web.StaticFileHandler, dict(path=settings['static_path'])),
                  ],
        **settings
    )
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    try:
        tornado.ioloop.IOLoop.instance().start()
        print('Server started...')
    except KeyboardInterrupt:
        print('Server has shut down.')

您的main() 方法可以解析命令行或使用parse_command_lineparse_config_file 解析配置文件:

tornado.options.parse_command_line()
# or
tornado.options.parse_config_file("/etc/server.conf")

【讨论】:

【参考方案3】:

在此处使用async/await 表示法,可能会适应您的 Python 版本:

class BaseHandler(tornado.web.RequestHandler):
    async def get_current_user(self):
        return await some_async_operation()

    async def prepare(self):
        self.current_user = await self.get_current_user()

Tornado 不会开箱即用地异步调用 get_current_user,但它会支持异步 prepare 方法,因此您可以在那里填充 self.current_user

【讨论】:

感谢您的帮助。但是执行代码时出现错误:500。 你必须在这里检查你的错误日志/激活调试,500 可能是很多东西。 哦,好吧,所以上面提到的更改应该理想地使代码异步工作。对吗? 是的。在def get 等中的代码中,您只需使用self.current_user;唯一与异步相关的地方是self.current_user 的填充方式,而这仅发生在prepare 中(覆盖self.current_user 填充方式的默认Tornado 实现)。 可能这里当然会触发一些错误,但您需要详细调查。 不是每个def函数前都要加上关键字async吗?

以上是关于异步登录龙卷风的主要内容,如果未能解决你的问题,请参考以下文章

python 龙卷风异步爬虫演示

python 一个简单的异步蜘蛛类。基于龙卷风

为异步龙卷风 Web 套接字服务器编写同步测试套件

将异步协程作为 celery 任务运行

python tornado获得多个异步httprequest的响应

Django + 龙卷风 |图片上传