美文网首页
tornado 系列讲解之一 (整体介绍 下)

tornado 系列讲解之一 (整体介绍 下)

作者: 落羽归尘 | 来源:发表于2019-09-20 21:30 被阅读0次

模板

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 官方文档

相关文章

网友评论

      本文标题:tornado 系列讲解之一 (整体介绍 下)

      本文链接:https://www.haomeiwen.com/subject/vcsxyctx.html