异步登录龙卷风
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()
在我的代码中,我有三个类BaseHandler
、MainHandler
和LoginHandler
。任何帮助将不胜感激。
【问题讨论】:
【参考方案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 引入了async
和await
关键字(使用这些关键字的函数也称为“本机协程”)。详情请见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_line
或parse_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吗?以上是关于异步登录龙卷风的主要内容,如果未能解决你的问题,请参考以下文章