当前位置 博文首页 > 云崖先生:Python Tornado系列(甩锅版)

    云崖先生:Python Tornado系列(甩锅版)

    作者:云崖先生 时间:2021-01-28 01:23

    tornado简介

       tornadoPython界中非常出名的一款Web框架,和Flask一样它也属于轻量级的Web框架。

       但是从性能而言tornado由于其支持异步非阻塞的特性所以对于一些高并发的场景显得更为适用。

       tornado简洁,高效,能够支持WebSocket,其I/O多路复用采用epoll模式来实现异步,并且还有Future期程对象来实现非阻塞。

       tornadoDjangoFlask等基于WSGI的框架有一个根本的区别,就是它实现socket的模块是自己写的,并不是用其他模块。

     A : socket部分B: 路由与视图函数对应关系(路由匹配)C: 模版语法
    django 别人的wsgiref模块 自己写 自己的(没有jinja2好用 但是也很方便)
    flask 别人的werkzeug(内部还是wsgiref模块) 自己写 别人的(jinja2)
    tornado 自己写的 自己写 自己写

      

    起步介绍

       如何编写一个最简单的tornado

    import os
    
    import tornado.ioloop
    import tornado.web
    
    BASE_DIR = os.path.dirname(__file__)
    
    
    class IndexHandler(tornado.web.RequestHandler):
        def get(self):
            self.render("index.html")
    
    
    settings = {
        "debug": True,
        "template_path": os.path.join(BASE_DIR, "views"),  # 存放模板的文件夹
        "static_path": os.path.join(BASE_DIR, "static"),  # 存放静态文件的文件夹
    }
    
    application = tornado.web.Application(
        [
            (r"/index", IndexHandler),  # 正则匹配,路由规则
        ],
        **settings)  # 配置项
    
    if __name__ == '__main__':
    	# 1.新增socket Server端,并将fp描述符添加至select或者epoll中
        application.listen(8888)
        # 2.循环epoll进行监听
        tornado.ioloop.IOLoop.instance().start()
    

       模板文件:

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
              content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>INDEX</title>
        <link rel="stylesheet" href="{{static_url('common.css')}}">
    <!--    <link rel="stylesheet" href="/static/common.css">-->
    </head>
    <body>
        <p>INDEX</p>
    </body>
    </html>
    

    HTTP请求处理方式

       下面将来探究Django/Flask/tornado如何处理一次HTTP请求:

       Django中处理一次HTTP请求默认是以多线程模式完成。

      在DJnago1.x版本之后,默认启动都是多线程形式,如果要启用单线程模式:

      python manage.py runserver --nothreading

    from django.shortcuts import HttpResponse
    from threading import get_ident
    
    def api1(request):
        print(get_ident())  # 13246
        return HttpResponse("api1")
    
    def api2(request):
        print(get_ident())  # 13824
        return HttpResponse("api2")
    

       Flask的底层其实也是wsgiref模块实现,所以处理一次HTTP请求也是以多线程。

    import flask
    from threading import get_ident
    
    app = flask.Flask(__name__)
    
    @app.route('/api1')
    def api1():
        print(get_ident())  # 15952
        return "api1"
    
    @app.route('/api2')
    def api2():
        print(get_ident())  # 15236
        return "api2"
    
    if __name__ == '__main__':
        app.run()
    

       tornado的处理方式是单线程+I/O多路复用,这意味着必须挨个挨个排队处理每一个HTTP请求:

    import tornado.ioloop
    import tornado.web
    from threading import get_ident
    
    
    class Api1Handler(tornado.web.RequestHandler):
        def get(self):
            print(get_ident())  # 10168
            self.write("api1")
    
    class Api2Handler(tornado.web.RequestHandler):
        def get(self):
            print(get_ident())  # 10168
            self.write("api2")
    
    application = tornado.web.Application([
        (r"/api1",Api1Handler),
        (r"/api2",Api2Handler),
    ])
    
    if __name__ == '__main__':
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    

    tornado的异步

       要想了解tornado的异步,就结合前面请求处理方式来看。

       同步Web框架与异步Web框架的区别,这里有一篇文章写的不错:

       同步与异步 Python 有何不同?

       上面文章的一句话来概括就是说同步大多数都是监听一个socket对象(服务器),当服务器对象的描述符状态(可读)发生改变后,就会创建一个新的线程来处理本次请求,Django/Flask内部其实都是通过wsgiref模块实现,并且wsgiref依赖于socketserver模块。如果想了解他们如何启动多线程进行服务监听,可参照早期文章(调用方式一模一样):

       socketserver使用及源码分析

       而对于tornado来说,它不会创建多线程,而是将conn双向连接对象放入事件循环中予以监听。

       得益于epoll的主动性,tornado的速度非常快,而在处理完conn(本次会话后),则会将connSocket)进行断开。 (HTTP短链接)

    tornado的非阻塞

       拿Django的单线程举例,当一个HTTP请求到来并未完成时,下一个HTTP请求将会被阻塞。

    python manage.py runserver --nothreading
    # 尝试以单线程的方式运行...对比tornado的单线程
    

       代码如下:

    from django.shortcuts import HttpResponse
    import time
    
    def api1(request):
        time.sleep(5)
        return HttpResponse("api1")
    
    def api2(request):
        return HttpResponse("api2")
    

       而如果是tornado的非阻塞方式,单线程模式下即使第一个视图阻塞了,第二个视图依旧能够进行访问.

    import time
    
    import tornado.ioloop
    import tornado.web
    from tornado import gen
    from tornado.concurrent import Future
    
    
    class Api1Handler(tornado.web.RequestHandler):
        @gen.coroutine
        def get(self):
            future = Future()
            # 方式一:添加回调 五秒后执行该异步任务 
            tornado.ioloop.IOLoop.current().add_timeout(time.time() + 5, self.done)
            # 方式二:添加future
            # tornado.ioloop.IOLoop.current().add_future(future,self.done)
            # 方式三:添加回调
            # future.add_done_callback(self.doing)
            yield future
    
        def done(self, *args, **kwargs):
            self.write('api1')
            self.finish()  # 完成本次HTTP请求,将future的result状态改变
    
    
    class Api2Handler(tornado.web.RequestHandler):
        def get(self):
            self.write("api2")
    
    
    application = tornado.web.Application([
        (r"/api1", Api1Handler),
        (r"/api2", Api2Handler),
    ])
    
    if __name__ == '__main__':
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    
    

       有关于Future对象如何实现异步,下面会进行详细的探讨。

    如何了解tornado

       tornado实现异步的根本技术点:I/O多路复用的epoll模式

       tornado实现非阻塞的根本技术点:Future期程(未来)对象

    tornado配置项

       仔细看起步介绍中,tornado的配置,它是作为关键字传参传入Application这个类中。

       所以我们可以使用**{k1:v1}的方式来设定配置项,下面举例一些常见的配置项。

    常规设置

       常规配置项:

    设置项描述
    autoreload 如果True,任何源文件更改时服务器进程将重新启动,如调试模式和自动重新加载中所述。此选项是Tornado 3.2中的新选项; 以前此功能由debug设置控制
    debug 几种调试模式设置的简写,在调试模式和自动重新加载中描述。设置debug=True相当于autoreload=True,compiled_template_cache=False,static_hash_cache=False,serve_traceback=True
    default_handler_class|default_handler_args 如果没有找到其他匹配项,将使用此处理程序; 使用它来实现自定义404页面(Tornado 3.2中的新增功能)
    compress_response 如果True,文本格式的响应将自动压缩。Tornado 4.0的新功能
    gzip compress_response自Tornado 4.0以来已弃用的别名
    log_function 此函数将在每个记录结果的请求结束时调用(使用一个参数,即RequestHandler对象)。默认实现将写入logging模块的根记录器。也可以通过覆盖来定制Application.log_request。
    serve_traceback 如果True,默认错误页面将包含错误的回溯。此选项是Tornado 3.2中的新选项; 以前此功能由debug设置控制
    ui_modules | ui_methods 可以设置为UIModule可用于模板的映射或UI方法。可以设置为模块,字典或模块和/或dicts列表。有关详细信息,请参阅UI模块。
    websocket_ping_interval 如果设置为数字,则每n秒钟将对所有websockets进行ping操作。这有助于通过关闭空闲连接的某些代理服务器保持连接活动,并且它可以检测websocket是否在未正确关闭的情况下发生故障。
    websocket_ping_timeout 如果设置了ping间隔,并且服务器在这么多秒内没有收到“pong”,它将关闭websocket。默认值是ping间隔的三倍,最少30秒。如果未设置ping间隔,则忽略。

       说点人话,debug或者autoreloadTrue时,修改源文件代码将会自动重启服务,相当于Django的重启功能。

       而log_function则可以自定制日志的输出格式,如下所示:

    def log_func(handler):
        if handler.get_status() < 400:
            log_method = access_log.info
        elif handler.get_status() < 500:
            log_method = access_log.warning
        else:
            log_method = access_log.error
            
        request_time = 1000.0 * handler.request.request_time()
        log_method("%d %s %s (%s) %s %s %.2fms",
                   handler.get_status(), handler.request.method,
                   handler.request.uri, handler.request.remote_ip,
                   handler.request.headers["User-Agent"],
                   handler.request.arguments,
    
    settings = {"log_function":log_func}
    

    身份/验证/安全

       关于身份、验证、安全的配置项:

    设置项描述
    cookie_secret 用于RequestHandler.get_secure_cookie 和set_secure_cookie签署cookie
    key_version set_secure_cooki 当cookie_secret是密钥字典时,requestHandler 使用特定密钥对cookie进行签名
    login_url authenticated如果用户未登录,装饰器将重定向到此URL。可以通过覆盖进一步自定义RequestHandler.get_login_url
    xsrf_cookies 如果True,将启用跨站点请求伪造保护
    xsrf_cookie_version 控制此服务器生成的新XSRF cookie的版本。通常应该保留默认值(它始终是支持的最高版本),但可以在版本转换期间临时设置为较低的值。Tornado 3.2.2中的新功能,它引入了XSRF cookie版本2
    xsrf_cookie_kwargs 可以设置为要传递给RequestHandler.set_cookie XSRF cookie 的其他参数的字典
    twitter_consumer_key 所用的 tornado.auth模块来验证各种API,如检测这些种类账号是否登录等...
    twitter_consumer_secret 同上..
    friendfeed_consumer_key 同上..
    friendfeed_consumer_secret 同上..
    google_consumer_key 同上..
    google_consumer_secret 同上..
    facebook_api_key 同上..
    facebook_secret 同上..

    模板设置

       模板设置项:

    设置项描述
    autoescape 控制模板的自动转义。可以设置为None禁用转义,或者设置 应该传递所有输出的函数的名称。默认为"xhtml_escape"。可以使用该指令在每个模板的基础上进行更改。{% autoescape %}
    compiled_template_cache 默认是True; 如果False每个请求都会重新编译模板。此选项是Tornado 3.2中的新选项; 以前此功能由debug设置控制
    template_path 包含模板文件的目录。可以通过覆盖进一步定制RequestHandler.get_template_path
    template_loader 分配给tornado.template.BaseLoader自定义模板加载的实例 。如果使用此 设置,则忽略template_path和autoescape设置。可以通过覆盖进一步定制RequestHandler.create_template_loader
    template_whitespace 控制模板中空格的处理; 查看tornado.template.filter_whitespace允许的值。Tornado 4.3中的新功能

    静态文件

       静态文件相关设置:

    设置项描述
    static_hash_cache 默认是True; 如果False 每次请求都会重新计算静态网址。此选项是Tornado 3.2中的新选项; 以前此功能由debug设置控制
    static_path 将从中提供静态文件的目录
    static_url_prefix 静态文件的Url前缀,默认为/static/
    static_handler_class | static_handler_args 可以设置为静态文件而不是默认文件使用不同的处理程序 tornado.web.StaticFileHandler。 static_handler_args如果设置,则应该是要传递给处理程序initialize方法的关键字参数的字典。

    url与路由

    正则匹配

       在tornado中,一个url对应一个类。

       匹配方式为正则匹配,因此要注意使用^$的使用。

       由于匹配行为是从上至下,所以在定义时一定要注意顺序。

    import tornado.ioloop
    import tornado.web
    
    # http://127.0.0.1:8888/admin
    class APIHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("...")
    
    
    settings = {"debug": True}
    
    application = tornado.web.Application([
        (r"^/a.{4}$", APIHandler),
    ], **settings)
    
    if __name__ == '__main__':
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    

    无名分组

       使用正则分组()解析出资源请求地址的一些参数。

       匹配到的参数会通过位置传参的形式传递给控制器处理函数,所以接收参数可以任意命名,因此你可以通过*args接收到所有匹配的参数:

    import tornado.ioloop
    import tornado.web
    
    # http://127.0.0.1:8888/register/yunya
    class RegisterHandler(tornado.web.RequestHandler):
        def get(self,*args):
            self.write(str(args))  # ('yunya',)
    
    
    settings = {"debug": True}
    
    application = tornado.web.Application([
        (r"^/register/(\w+)", RegisterHandler),
    ], **settings)
    
    if __name__ == '__main__':
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    
    

       如果确定这一被捕捉参数将被使用,则可指定形参进行接收:

    import tornado.ioloop
    import tornado.web
    
    # http://127.0.0.1:8888/register/yunya
    class RegisterHandler(tornado.web.RequestHandler):
        def get(self,params):
            self.write(params)   # 'yunya'
    
    
    settings = {"debug": True}
    
    application = tornado.web.Application([
        (r"^/register/(\w+)", RegisterHandler),
    ], **settings)
    
    if __name__ == '__main__':
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    

    有命分组

       使用正则的有命分组(?P<组名>规则)解析出资源请求地址的一些参数。

       匹配到的参数会通过关键字传参的形式传递给控制器处理函数,所以接收参数必须与组名相同,因此你可以通过**kwargs接收到所有匹配的参数:

    import tornado.ioloop
    import tornado.web
    
    # http://127.0.0.1:8888/register/yunya
    class RegisterHandler(tornado.web.RequestHandler):
        def get(self,**kwargs):
            self.write(str(kwargs))  # {'username': 'yunya'}
    
    
    settings = {"debug": True}
    
    application = tornado.web.Application([
        (r"^/register/(?P<username>\w+)", RegisterHandler),
    ], **settings)
    
    if __name__ == '__main__':
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    

       如果确定这一被捕捉参数将被使用,则可指定形参进行接收(形参命名必须与正则匹配的组名相同):

    import tornado.ioloop
    import tornado.web
    
    # http://127.0.0.1:8888/register/yunya
    class RegisterHandler(tornado.web.RequestHandler):
        def get(self,username):
            self.write(username)  # 'yunya'
    
    
    settings = {"debug": True}
    
    application = tornado.web.Application([
        (r"^/register/(?P<username>\w+)", RegisterHandler),
    ], **settings)
    
    if __name__ == '__main__':
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    

    混合使用

       在tornado中,路由匹配的参数捕捉不允许无名分组和有名分组的混合使用,这会引发一个异常:

    application = tornado.web.Application([
        (r"^/register/(/d+)/(?P<username>\w+)", RegisterHandler),
    ], **settings)
    

       抛出的错误:

    AssertionError: groups in url regexes must either be all named or all positional: '^/register/(/d+)/(?P<username>\\w+)$'
    

       分组必须全部使用位置、或者使用命名。

    反向解析

       反向解析要与路由命名同时使用:

    import tornado.ioloop
    import tornado.web
    
    
    # http://127.0.0.1:8888/register
    class RegisterHandler(tornado.web.RequestHandler):
        def get(self):
            print(self.reverse_url("reg"))  # /register
            self.write("注册页面")
    
    
    settings = {"debug": True}
    
    # 使用tornado.web.url来进行添加路由规则
    application = tornado.web.Application([
        tornado.web.url(r'/register', RegisterHandler, name="reg")
    ], **settings)
    
    if __name__ == '__main__':
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    
    

       前端模板中的反向解析(必须注册名字):

    {{reverse_url('reg')}}
    

    控制器

    基本概念

       在MCV模型中,C代表Controller即为控制器,类似于Django中的views

       我们可以看见在tornado中,控制器处理函数都毫不意外的继承了一个叫做tornado.web.RequestHandler的对象,所有的方法都是从self中进行调用,所以你可以查看它的源码获取所有方法,或者使用help()函数获得它的DOC

       下面我将例举出一些常用的方法。

    获取相关

       以下是常用的获取相关属性以及方法,基本是RequestHandler中的属性、方法与self.request对象中封装的方法和属性:

    属性/方法描述
    self.request 获取用户请求相关信息
    self._headers 获取请求头信息,基本请求头被过滤
    self.request.headers 获取请求头信息,包含基本请求头
    slef.request.body 获取请求体信息,bytes格式
    self.request.remote_ip 获取客户端IP
    self.request.method 获取请求方式
    self.request.version 获取所使用的HTTP版本
    self.get_query_argument() 获取单个GET中传递过来的参数,如果多个参数同名,获取最后一个
    slef.get_query_arguments() 获取所有GET中传递过来的参数,返回列表的形式
    self.get_body_argument() 获取单个POST中传递过来的参数,如果多个参数同名,获取最后一个
    self.get_body_arguments() 获取所有POST中传递过来的参数,返回列表的形式
    self.get_argument() 获取单个GET/POST中传递过来的参数,如果多个参数同名,获取最后一个
    self.get_arguments() 获取所有GET/POST中传递过来的参数,返回列表的形式
    self.request.files 获取所有通过 multipart/form-data POST 请求上传的文件
    self.request.host 获取主机名
    self.request.uri 获取请求的完整资源标识,包括路径和查询字符串
    self.request.query 获取查询字符串的部分
    self.request.path 获取请求的路径( ?之前的所有内容)

       示例演示:

    import tornado.ioloop
    import tornado.web
    
    
    # http://127.0.0.1:8888/register?name=yunya&hobby=%E7%AF%AE%E7%90%83&hobby=%E8%B6%B3%E7%90%83
    class RegisterHandler(tornado.web.RequestHandler):
        def get(self):
            # 获取客户端IP
            print(self.request.remote_ip)  # 127.0.0.1
            # 查看请求方式
            print(self.request.method)  # GET
            # 获取单个GET/POST传递的参数
            print(self.get_query_argument("name"))  # yunya
            # 获取多个GET/POST传递的参数、list形式
            print(self.get_query_arguments("hobby"))  # ['篮球', '足球']
            
            print(self.request.host) # 127.0.0.1:8888
            print(self.request.uri)  # register?name=yunya&hobby=%E7%AF%AE%E7%90%83&hobby=%E8%B6%B3%E7%90%83
            print(self.request.path)  # /register
            print(self.request.query)  # name=yunya&hobby=%E7%AF%AE%E7%90%83&hobby=%E8%B6%B3%E7%90%83
            
            self.write("OK")
    
    settings = {"debug":True}
    application = tornado.web.Application([
        tornado.web.url(r'/register', RegisterHandler, name="reg")
    ], **settings)
    
    if __name__ == '__main__':
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    

       文件上传案例:

    import tornado.ioloop
    import tornado.web
    
    class APIHandler(tornado.web.RequestHandler):
        def post(self):
            # step01:获取所有名为img的文件对象,返回一个列表 [img,img,img]
            file_obj_list = self.request.files.get("img")
            # step02:获取第一个对象
            file_obj = file_obj_list[0]
            # step03:获取文件名称
            file_name = file_obj.filename
            # step04:获取文件数据
            file_body = file_obj.body
            # step05:获取文件类型
            file_type = file_obj.content_type
    
            with open(f"./{file_name}",mode="wb") as f:
                f.write(file_body)
    
            self.write("OK")
    
    settings = {"debug":True}
    application = tornado.web.Application([
        tornado.web.url(r'/api', APIHandler)
    ], **settings)
    
    if __name__ == '__main__':
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    
    

    响应相关

       响应一般就分为以下几种,返回单纯的字符串,返回一个模板页面,返回JSON格式数据,返回一个错误,以及重定向:

       返回单纯字符串:

    self.write("OK")
    

       返回一个模板页面:

    self.render("templatePath",**kwargs)  # 传递给模板的数据
    

       返回JSON格式数据(手动JSON):

    import json
    json_data = json.dumps({"k1":"v1"})
    self.write(json_data)
    

       返回一个错误(直接raise引发异常即可):

    raise tornado.web.HTTPError(403) 
    

       重定向:

    self.redirect("/",status=301)
    

       响应头相关的操作:

    self.set_header("k1", 1)
    self.add_header("k2", 2)
    self.clear_header("k1") 
    

    钩子函数

       我们可以在控制器中定义一个钩子函数initialize(),并且可以在url中对他进行一些参数传递:

    import tornado.ioloop
    import tornado.web
    
    
    class APIHandler(tornado.web.RequestHandler):
        def initialize(self, *args, **kwargs) -> None:
            print(kwargs)  # {k1:v1}
            self.data = "某个数据"
    
        def post(self):
            print(self.data)  # 某个数据
            self.write("ok")
    
    
    settings = {"debug": True}
    application = tornado.web.Application([
        tornado.web.url(r'/api', APIHandler, {"k1": "v1"}),  # dict -> **kwargs
    ], **settings)
    
    if __name__ == '__main__':
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    

       所有的钩子函数:

    class APIHandler(tornado.web.RequestHandler):
        def set_default_headers(self):
            print("first--设置headers")
    
        def initialize(self):
            print("second--初始化")
    
        def prepare(self):
            print("third--准备工作")
    
        def get(self):
            print("fourth--处理get请求")
    
        def post(self):
            print('fourth--处理post请求')
    
        def write_error(self, status_code, **kwargs):
            print("fifth--处理错误")
    
        def on_finish(self):
            print("sixth--处理结束,释放资源--")
    

    模板

    指定目录

       在settings中指定模板所在目录,如不指定,默认在当前文件夹下:

    import tornado.ioloop
    import tornado.web
    
    
    class APIHandler(tornado.web.RequestHandler):
        def get(self):
            # 找当前目录下的views文件夹,到views下去找api.html模板文件
            self.render("api.html")
    
    
    settings = {
        "debug": True,
        "template_path": "views",  # 指定模板目录
        "static_path": "static",  # 指定静态文件目录
    }
    
    application = tornado.web.Application([
        tornado.web.url(r'/api', APIHandler),
    ], **settings)
    
    if __name__ == '__main__':
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    
    

    模板传参

       tornado中的模板传参与Flask相同。

       模板传参可以通过k=v的方式传递,也可以通过**dict的方式进行解包传递:

    class APIHandler(tornado.web.RequestHandler):
        def get(self):
            context = {
                "name": "云崖",
                "age": 18,
                "hobby": ["篮球", "足球"]
            }
            self.render("api.html",**context)
            # self.render("api.html",name="云崖",age=18,hobby=["篮球", "足球"])
    

       渲染,通过{{}}进行,注意:trdtmp中不支持.的深度查询访问,这点与DTLJinJa2不同:

    <body>
        <p>{{name}}</p>
        <p>{{age}}</p>
        <p>{{hobby[0]}}-{{hobby[1]}}</p>
    </body>
    

    模板形式

       模板中有两种表现形式,一种是表达式形式以{{}}进行包裹,另一种是命令形式以{% 命令 %}包裹。

      注意:tornado的模板语法的结束标识是{% end %},不是Django或jinja2的{% endblock %}

       举例表达式形式:

    # 渲染控制器函数传入的变量
    <body>
    欢迎{{ username }}登录
    </body>
    
    # 进行Python表达式
    {{ 1 + 1 }}
    # 导入模块并使用
    {{ time.time() }}
    

       举例命令形式:

    {% if 1 %}
        this is if
    {% end %}
    

       如果想对命令进行注释,则可以使用{# #},如果不想执行内容,则可以使用{{! {%! {#为前缀,如下示例:

    {{! 1 + 1}}
    
    {%! if 1 %}
        this is if
    {%! end %}
    
    {#! time.time() #}}
    

    导入模块

       tornado中的模板语言允许导入Python包、模块:

    {% import time %}
    {{ time.time() }}
    
    {% from util.modify import mul %}
    {{mul(6,6)}}
    

    模板功能

       模板中提供一些功能,可以在{{}}或者{% %}中进行使用:

    模板调用的方法/功能/模块描述
    escape tornado.escape.xhtml_escape的别名
    xhtml_escape tornado.escape.xhtml_escape的别名
    url_escape tornado.escape.url_escape的别名
    json_encode tornado.escape.json_encode的别名
    squeeze tornado.escape.squeeze的别名
    linkify tornado.escape.linkify的别名
    datetime Python 的 datetime模组
    handler 当前的 RequestHandler对象
    request handler.request的別名
    current_user handler.current_user的别名
    locale handler.locale`的別名
    _ handler.locale.translate 的別名
    static_url for handler.static_url 的別名
    xsrf_form_html handler.xsrf_form_html 的別名
    reverse_url Application.reverse_url 的別名
    Application 设置中ui_methods和 ui_modules下面的所有项目

    分支循环

       模板中的if判断:

    {% if username != 'no' %}
        欢迎{{ username }}登录
    {% else %}
        请登录
    {% end %}
    

       for循环:

    <body>
        {% for item in range(10) %}
            {% if item == 0%}
                <p>start</p>
            {% elif item == len(range(10))-1 %}
                <p>end</p>
            {% else %}
                <p>{{item}}</p>
            {% end %}
        {% end %}
    </body>
    

       while循环:

    {% set a = 0 %}
    
    {% while a<5 %}
        {{ a }}<br>
        {% set a += 1 %}
    {% end %}
    

    模板转义

       默认的模板在渲染时都会将<>以及空格等特殊字符替换为HTML内容,如&lt;&gt;等。

       关于模板转义的方式有以下几种。

       1.单变量去除转义:

    {{'<b>你好</b>'}}   	 # &lt;b&gt;你好&lt;/b&gt;
    {% raw '<b>你好</b>' %}  # <b>你好</b>
    

       2.当前模板全局去除转义:

    {% autoescape None %}  # 模板首行加入
    

       3.整个项目去掉转义,为当前的application进行配置:

    settings = {
        "autoescape":None, # 禁用转义
    }
    

    模板继承

       使用{% extends %}引入一个定义好的模板。

       使用{% blocak %}{% end %}定义并替换块。

       定义主模板:

    # views/base.html
    
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
              content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>{% block title %}Document{% end %}</title>
        {% block css %}
        {% end %}
    </head>
    <body>
        {% block main %}
        {% end %}
    </body>
    {% block js %}
    {% end %}
    </html>
    

       继承与使用模板:

    {% extends  'base.html'%}
    
    {% block title %}
    API
    {% end %}
    
    {% block css %}
    <style>
        body{
            background-color: red;
        }
    </style>
    {% end %}
    
    {% block main %}
    <p>HELLO</p>
    {% end %}
    
    {% block js %}
    <script>
        alert("HELLO")
    </script>
    {% end %}
    

       

    模板引入

       如果一个地方需要一块完整的模板文件,则使用模板引入即可:

       {include 'templateName'}

       定义公用模板:

    # views/common.html
    
    <h1>公用内容</h1>