python Tornado学习

是什么?

Tornado是使用Python编写的一个强大的、可扩展的Web服务器

运行服务:

真正使得Tornado运转起来的语句。首先,我们使用Tornado的options模块来解析命令行。然后我们创建了一个Tornado的Application类的实例。传递给Application类__init__方法的最重要的参数是handlers。它告诉Tornado应该用哪个类来响应请求。马上我们讲解更多相关知识。

1
2
3
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()

从这里开始的代码将会被反复使用:一旦Application对象被创建,我们可以将其传递给Tornado的HTTPServer对象,然后使用我们在命令行指定的端口进行监听(通过options对象取出。)最后,在程序准备好接收HTTP请求后,我们创建一个Tornado的IOLoop的实例。

模板语法

一个Tornado模板仅仅是用一些标记把Python控制序列和表达式嵌入 HTML(或者任意其他文本格式)的文件中:

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<ul>
{% for item in items %}
<li>{{ escape(item) }}</li>
{% end %}
</ul>
</body>
</html>

如果你把这个目标保存为”template.html”并且把它放在你Python文件的 相同目录下, 你可以使用下面的代码渲染它:

1
2
3
4
class MainHandler(tornado.web.RequestHandler):
def get(self):
items = ["Item 1", "Item 2", "Item 3"]
self.render("template.html", title="My title", items=items)

Tornado模板支持 控制语句(control statements)表达式(expressions). 控制语句被包在 {%` 和 `%} 中间, e.g., {% if len(items) > 2 %}. 表达式被包在 {{` 和 `}} 之间, e.g., {{ items[0] }}.

控制语句或多或少都和Python语句类似. 我们支持 if, for, while, 和 try, 这些都必须使用 {% end %} 来标识结束. 我们也 支持 模板继承(template inheritance) 使用 extendsblock 标签声明, 这些内容的详细信息都可以在 tornado.template 中看到.

表达式可以是任意的Python表达式, 包括函数调用. 模板代码会在包含以下对象 和函数的命名空间中执行 (注意这个列表适用于使用 RequestHandler.renderrender_string 渲染模板的情况. 如果你直接在 RequestHandler 之外使用 tornado.template 模块, 下面这些很多都不存 在).

当你正在构建一个真正的应用, 你可能想要使用Tornado模板的所有特性, 尤其是目标继承. 阅读所有关于这些特性的介绍在 tornado.template 部分 (一些特性, 包括 UIModules 是在 tornado.web 模块中实现的)

在引擎下, Tornado模板被直接转换为Python. 包含在你模板中的表达式会 逐字的复制到一个代表你模板的Python函数中. 我们不会试图阻止模板语言 中的任何东西; 我们明确的创造一个高度灵活的模板系统, 而不是有严格限制 的模板系统. 因此, 如果你在模板表达式中随意填充(代码), 当你执行它的时 候你也会得到各种随机错误.

所有模板输出默认都会使用 tornado.escape.xhtml_escape 函数转义. 这个行为可以通过传递 autoescape=NoneApplication 或者 tornado.template.Loader 构造器来全局改变, 对于一个模板文件可以使 用 {% autoescape None %} 指令, 对于一个单一表达式可以使用 {% raw ...%} 来代替 {{ ... }}. 此外, 在每个地方一个可选的 转义函数名可以被用来代替 None.

注意, 虽然Tornado的自动转义在预防XSS漏洞上是有帮助的, 但是它并不能 胜任所有的情况. 在某一位置出现的表达式, 例如Javascript 或 CSS, 可能需 要另外的转义. 此外, 要么是必须注意总是在可能包含不可信内容的HTML中 使用双引号和 xhtml_escape , 要么必须在属性中使用单独的转义函数 (参见 e.g. http://wonko.com/post/html-escaping)

认证和安全

Cookies 和 secure cookies

你可以在用户浏览器中通过 set_cookie 方法设置 cookie:

1
2
3
4
5
6
7
class MainHandler(tornado.web.RequestHandler):
def get(self):
if not self.get_cookie("mycookie"):
self.set_cookie("mycookie", "myvalue")
self.write("Your cookie was not set yet!")
else:
self.write("Your cookie was set!")

普通的cookie并不安全, 可以通过客户端修改. 如果你需要通过设置cookie, 例如来识别当前登录的用户, 就需要给你的cookie签名防止伪造. Tornado 支持通过 set_secure_cookieget_secure_cookie 方法对cookie签名. 想要使用这 些方法, 你需要在你创建应用的时候, 指定一个名为 cookie_secret 的密钥. 你可以在应用的设置中以关键字参数的形式传递给应用程序:

1
2
3
application = tornado.web.Application([
(r"/", MainHandler),
], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")

签名后的cookie除了时间戳和一个 HMAC 签名还包含编码 后的cookie值. 如果cookie过期或者签名不匹配, get_secure_cookie 将返回 None 就像没有设置cookie一样. 上面例子的安全版本:

1
2
3
4
5
6
7
class MainHandler(tornado.web.RequestHandler):
def get(self):
if not self.get_secure_cookie("mycookie"):
self.set_secure_cookie("mycookie", "myvalue")
self.write("Your cookie was not set yet!")
else:
self.write("Your cookie was set!")

Tornado的安全cookie保证完整性但是不保证机密性. 也就是说, cookie不能被修改 但是它的内容对用户是可见的. 密钥 cookie_secret 是一个对称的key, 而且必 须保密–任何获得这个key的人都可以伪造出自己签名的cookie.

默认情况下, Tornado的安全cookie过期时间是30天. 可以给 set_secure_cookie 使用 expires_days 关键字参数 同时 get_secure_cookie 设置 max_age_days 参数也可以达到效果. 这两个值分别通过这样(设置)你就可以达 到如下的效果, 例如大多数情况下有30天有效期的cookie, 但是对某些敏感操作(例 如修改账单信息)你可以使用一个较小的 max_age_days .

Tornado也支持多签名密钥, 使签名密钥轮换. 然后 cookie_secret 必须是一个 以整数key版本作为key, 以相对应的密钥作为值的字典. 当前使用的签名键 必须是 应用设置中 key_version 的集合. 不过字典中的其他key都允许做 cookie签名验证, 如果当前key版本在cookie集合中.为了实现cookie更新, 可以通过 get_secure_cookie_key_version 查询当前key版本.

用户认证

当前已经通过认证的用户在每个请求处理函数中都可以通过 self.current_user 得到, 在每个模板中 可以使用 current_user 获得. 默认情况下, current_userNone.

为了在你的应用程序中实现用户认证, 你需要在你的请求处理函数中复写 get_current_user() 方法来判断当前用户, 比如可以基于cookie的值. 这里有一个例子, 这个例子允许用户简单的通过一个保存在cookie中的特殊昵称 登录到应用程序中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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("Hello, " + name)

class LoginHandler(BaseHandler):
def get(self):
self.write('<html><body><form action="/login" method="post">'
'Name: <input type="text" name="name">'
'<input type="submit" value="Sign in">'
'</form></body></html>')

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

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

你可以使用 Python 装饰器(decorator) tornado.web.authenticated 要求用户登录. 如果请求方法带有这个装饰器 并且用户没有登录, 用户将会被重定向到 login_url (另一个应用设置). 上面的例子可以被重写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MainHandler(BaseHandler):
@tornado.web.authenticated
def get(self):
name = tornado.escape.xhtml_escape(self.current_user)
self.write("Hello, " + name)

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

如果你使用 authenticated 装饰 post() 方法并且用户没有登录, 服务将返回一个 403 响应. @authenticated 装饰器是 if not self.current_user: self.redirect() 的简写. 可能不适合 非基于浏览器的登录方案.

通过 Tornado Blog example application 可以看到一个使用用户验证(并且在MySQL数据库中存储用户数据)的完整例子.

代码模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import tornado
import tornado.ioloop
settings={"cookie_secret":"内容"}
class indexhandler(tornado.web.RequestHandler):
def get(self):
self.set_secure_cookie("username","cookie值的内容")
self.write(self.set_secure_cookie("username"))
if __name__ == "__main__":
app=tornado.web.Application(
[
(r"/",indexhandler),
],**settings#cooki_secret:xxxx的简写
)
app.listen(8004)#端口注意别冲突
tornado.ioloop.IOLoop.current().start()
Author

vague huang

Posted on

2021-03-03

Updated on

2021-03-05

Licensed under

Comments