模板
tornado包含有灵活的模板语言,为了配置不同目录下的模板文件,可以使用Application setting下的 template_path
配置,如果有不同的模板路径,也可以使用RequestHandler.get_template_path
重载。
模板语法
例文件template.html:
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<ul>
{% for item in items %}
<li>{{ escape(item) }}</li>
{% end %}
</ul>
</body>
</html>
可以使用render这个模板通过:
class MainHandler(tornado.web.RequestHandler):
def get(self):
items = ["Item 1", "Item 2", "Item 3"]
self.render("template.html", title="My title", items=items)
将title和items变量传到template.html文件中。
tornado支持控制语句和表达式。
- 控制语句用{% and %}包裹起来,如:{% if len(items) > 2 %};
- 表达式用 {{ and }}包裹。
控制语句或多或少和python语句类似,支持if, for,while,try,这些语句要以{% end %}作为结束标志。
也有extends 和 block语句,具体可以在tornado.template
中查看。
权限和安全
cookies
可以通过set_cookie设置cookie:
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是不安全的,易被客户端修改。tornado支持安全的签名cookie,提供的对应方法: set_secure_cookie
and get_secure_cookie
。使用之前需要先指定一个secret key,名字为cookie_secret,在创建application的时候。
application = tornado.web.Application([
(r"/", MainHandler),
], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")
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是一个对称秘钥,必须保密,任何人获取到这个秘钥都能生成自己的签名cookie。
默认情况下,tornado的安全cookie有30天有效期。可以通过expires_days设置。
用户身份验证
当前有效用户可以通过self.current_user
获得,在模板中是current_user,默认为None。
为了实现身份验证,需要实现get_current_user()方法:
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__")
如果想要用户登录的前提下访问app,可以使用装饰器tornado.web.authenticated
,如果没有登录,会重定向到login_url,可以在settings中配置地址:
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)
如果将这个装饰器装饰在post方法上,没有登录的话,服务器会返回403错误。
运行和部署
tornado提供了自己的http服务器,启动一个服务可以通过下面方式:
def main():
app = make_app()
app.listen(8888)
IOLoop.current().start()
if __name__ == '__main__':
main()
多进程和端口
由于python GIL的存在,为了获取更好的性能,可以运行多个python进程
def main():
app = make_app()
server = tornado.httpserver.HTTPServer(app)
server.bind(8888)
server.start(0) # forks one process per cpu
IOLoop.current().start()
这是开启多进程最简单的方式,并且这些进程共享同一端口。但是这种方式有些限制:
- 每一个子进程,都有自己的IOloop,因此,fork新进程之前,不能有全局的IOloop。
- it is difficult to do zero-downtime updates in this model
- 所有进程共享端口,单独监控很困难。
使用负载均衡
最后每个进程都有自己的端口,可以通过Nginx的负载均衡配置:
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
}
http {
# Enumerate all the Tornado servers here
upstream frontends {
server 127.0.0.1:8000;
server 127.0.0.1:8001;
server 127.0.0.1:8002;
server 127.0.0.1:8003;
}
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
keepalive_timeout 65;
proxy_read_timeout 200;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
gzip on;
gzip_min_length 1000;
gzip_proxied any;
gzip_types text/plain text/html text/css text/xml
application/x-javascript application/xml
application/atom+xml text/javascript;
# Only retry if there was a communication error, not a timeout
# on the Tornado server (to avoid propagating "queries of death"
# to all frontends)
proxy_next_upstream error;
server {
listen 80;
# Allow file uploads
client_max_body_size 50M;
location ^~ /static/ {
root /var/www;
if ($query_string) {
expires max;
}
}
location = /favicon.ico {
rewrite (.*) /static/favicon.ico;
}
location = /robots.txt {
rewrite (.*) /static/robots.txt;
}
location / {
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_pass http://frontends;
}
}
}
静态文件和文件缓存
通过指定 static_path配置静态文件:
settings = {
"static_path": os.path.join(os.path.dirname(__file__), "static"),
"cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
"login_url": "/login",
"xsrf_cookies": True,
}
application = tornado.web.Application([
(r"/", MainHandler),
(r"/login", LoginHandler),
(r"/(apple-touch-icon\.png)", tornado.web.StaticFileHandler,
dict(path=settings['static_path'])),
], **settings)
这样所有请求请求静态文件时,都会自动添加/static/,如:http://localhost:8888/static/foo.png
。
在模板中,可以使用static_url
<html>
<head>
<title>FriendFeed - {{ _("Home") }}</title>
</head>
<body>
<div><img src="{{ static_url("images/logo.png") }}"/></div>
</body>
</html>
配置Nginx的时候,也可以设置缓存:
location /static/ {
root /var/friendfeed/static;
if ($query_string) {
expires max;
}
}
参考
tornado 官方文档
网友评论