模板

走个流程

  1. 配置模板路径

    1
    2
    3
    4
    settings = {
    'static_path':os.path.join(BASE_DIRS,'static'),
    'template_path':os.path.join(BASE_DIRS,'templates'),
    }
  2. 模板为

    1
    2
    3
    4
    5
    <head>
    <link rel="stylesheet" type="text/css" href="{{ static_url('css/style.css') }}">
    </head>

    <li>{{ num }}</li>
  3. 使用self.render

    1
    2
    3
    4
    5
    6
    class TestHandler5(tornado.web.RequestHandler):
    def get(self,*args,**kwargs):
    context = {
    'num':55,
    }
    self.render('index2.html',**context)

变量与表达式

  • 变量 : {{ var }}

  • tornado模板系统更为强大 , 支持表达式{{ expression }}

    • {{ num +10 }}
    • {{ num==200 }}
  • 这就造成了在tornado中,不能使用Django中的点号语法:

    1
    2
    3
    4
    5
    6
    7
    class TestHandler5(tornado.web.RequestHandler):
    def get(self,*args,**kwargs):
    context = {
    'num':55,
    'd':{'name':'hyl'},
    }
    self.render('index2.html',**context)
    1
    2
    3
    <a>{{ d }}</a>
    {# 使用字典的索引 #}
    <a>{{ d['name'] }}</a>

    在jango中是

    1
    2
    3
    <a>{{ d }}</a>
    {# 使用点号索引 #}
    <a>{{ d.name }}</a>

流程控制

  • if

    1
    2
    3
    4
    5
    {{% if 表达式 %}}
    语句
    {{% elif 表达式 %}}
    语句
    {{% end %}}

    Django是{{% endif %}}

  • for

    1
    2
    3
    {{% for 变量 in 集合 %}}
    语句
    {{% end %}}

    Django是{{% endfor %}}

  • while

函数

static_url()

1
2
3
4
settings = {
'static_path':os.path.join(BASE_DIRS,'static'),
'template_path':os.path.join(BASE_DIRS,'templates'),
}
1
2
3
4
5
<head>
<link rel="stylesheet" type="text/css" href="{{ static_url('css/style.css') }}">
</head>

<li>{{ num }}</li>
  • static_url创建了一个基于文件内容的hash值,并将其添加到URL末尾当一个查词参数
  • 这个hash值总能保证加载的都是最新的文件,而不是以前的缓存版本,
  • 不论是开发阶段还是上线阶段都是很有必要的

自定义函数

在Handler里面定义函数 , 然后将其传给模板

1
2
3
4
5
6
7
8
9
class TestHandler5(tornado.web.RequestHandler):
def get(self,*args,**kwargs):
def mysum(a,b):
return a+b

context = {
'mysum':mysum,
}
self.render('index2.html',**context)
1
<li>{{ mysum(100,2) }}</li>

转义

  • tornado默认开启了自动转义功能,能防止网站受到恶意攻击

  • 关闭转义:

    • raw标签 : 单次关闭 {{% raw data %}}

      1
      2
      3
      4
      5
      6
      class TestHandler8(tornado.web.RequestHandler):
      def get(self,*args,**kwargs):
      context = {
      'data':'<a>a标签</a>'
      }
      return self.render('index2.html',**context)
      1
      <li>{% raw data %}</li>
    • autoescape标签 : 关闭当前模板{% autoescape None %}

    • 在配置中设置: 关闭全部模板

      1
      2
      3
      settings = {
      'autoescape' : None
      }
    • escape()函数 : 在关闭自动转义后,可以使用该方法刘特定的变量进行转义

      1
      2
      3
      4
      5
      {% autoescape None %}
      {{ data }}
      <!-- 转义 -->
      {{ escape(data) }}
      {{ data }}

继承

1
2
3
4
5
6
<!-- base.html -->

<!-- 挖坑 -->
{% block main %}

{% end %}
1
2
3
4
5
{% extends 'base.html' %}
<!-- 填坑 -->
{% block main %}

{% end %}

静态文件

static_url()

  • 作用 : 告诉 tornado从文件系统中的某一个特定的位置提供静态文件 : 'static_path':os.path.join(BASE_DIRS,'static'),

  • 一般我们的static文件下的子文件夹为css,js,img . 有可能还有html页面 . 也就是说,我们可以直接访问这个html页面资源

    • 访问www.baidu.com/index.html 返回百度首页 , 此时并不是使用url的重定向 , 而是直接访问index.html这个文件
    • 就像我们使用{% static css/style.css %}一样,获取{% static html/index.html %}
    • 一般这种html都是静态的 , 意思就是这个html不含有{{ var }} ,{% if... %}等变量或标签

    也就是说说 ,当前的staic文件夹如下:

    1
    2
    3
    4
    5
    6
    static
    ├─js
    ├─img
    ├─css
    └─html
    └─index.html

    通过http://127.0.0.1:8000/static/html/index.html来直接访问index.html这个页面

  • 但是http://127.0.0.1:8000/static/html/index.html层次过深 , 我们希望改为http://127.0.0.1:8000/index.html . 此时就需要使用到StaticFileHandler对象

StaticFileHandler对象

  • 使用原因 : http://127.0.0.1:8000/static/html/index.html对于用户来说体验不佳

  • 本质 : tornado预制的用来提供静态资文件的 handler

  • 作用 : 通过tornado.web.StaticFileHandler来映射静态文件

  • 使用 :
    只要在路由里添加:

    1
    2
    3
    4
    # 注意在最后添加此路由
    # path为文件查找的路径
    # default_filename为默认寻找的文件
    (r'/(.*)$',tornado.web.StaticFileHandler,{'path':os.path.join(config.BASE_DIRS,'static/html')},'default_filename':'index.html'),

    然后在static/html添加index.html,main.html等等 ,当用户访问http://127.0.0.1:8000/index.html,http://127.0.0.1:8000/main.html的时候就可以直接映射寻找文件了

    • path : 用来指定提供静态文件的根路径
    • default_filename : 用来指定访同路由中末指明文件名时,默认提供的静态文件

数据库

tornado没有自带的ORM,对于数据库需要自己去适配

  1. 类似于aiohttp , 在应用启动时创建一个数据库链接实例,供各个 RequestHandler 使用
  2. 在 RequestHandler 中通过 self.application来获取其应用对象
    (如 : self.application.db)

应用安全

Cookies

普通cookies

  • 设置

    1
    self.set_cookie(name,value,domain=None,expires=None,path='/',expries_days=None,**kwargs)
    • domain : 提交cookie时匹配的域名
    • path : 提交cookie是匹配的路径
    • expires : 有效期,可以是时间戳 , 时间元组 , datetime
    • expries_days : 有效期天数 , 优先级低于expires
    1
    2
    3
    4
    class TestHandler9(tornado.web.RequestHandler):
    def get(self,*args,**kwargs):
    self.set_cookie('name','hyl',expires=60*60*24)
    self.write('hello')
    • 设置cookie实际上就是设置header的set_cookie字段
    • 可以这么写 :
    1
    self.set_header('Set-Cookie','name=hyl; Path=/')
  • 获取

    1
    2
    3
    4
    5
    class TestHandler9(tornado.web.RequestHandler):
    def get(self,*args,**kwargs):
    # '未登录'是default,当不存在时被赋值
    self.get_cookie('name','未登录')
    self.write('hello')
  • 清除:

    • clear_cookie()
    • clear_all_cookie()
    1
    2
    3
    4
    class TestHandler9(tornado.web.RequestHandler):
    def get(self,*args,**kwargs):
    self.clear_cookie('name')
    self.write('hello')

    删除名为name,井同时匹配 domain 和 path 的 cookie

    • 执行清除 Cookie操作后,并不是立即删除浏器的cookie,而是给 cookie 值设置空,并改其有限期限为失效,
    • 真正删除cookie是由浏克器白己去清理的

安全cookies

  • Cookie是存在客户浏览需的数据,很容易被篡改,Tornado提供了一种对 Cockie进行简易加密方式,来防止Cookie被篡改

  • 设置 :

    1. 需要为应用配置一个用来给 Cookie进行混清加密的秘钥

      生成密钥

      1
      2
      3
      4
      import base64
      import uuid
      print(base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes))
      # 'fq/tqtjCRGG9kIAOtu2QQp0MbOtQ8kxdnYsS8lLQZlg='
    2. 将密钥放入cookie_secret

      1
      2
      3
      4
      5
      6
      7
      # config.py
      settings = {
      'debug' : True,
      'cookie_secret':'fq/tqtjCRGG9kIAOtu2QQp0MbOtQ8kxdnYsS8lLQZlg='
      'static_path':os.path.join(BASE_DIRS,'static'),
      'template_path':os.path.join(BASE_DIRS,'templates'),
      }
    3. set_secure_cookie : 设置一个带有签名和时间的 cookie,防止cookie被伪造

      1
      self.set_secure_cookie(name,value,expires_day=30,version=None,**kwargs)
    4. 示例:

      1
      2
      3
      4
      class TestHandler9(tornado.web.RequestHandler):
      def get(self,*args,**kwargs):
      self.set_secure_cookie('name','hyl')
      self.write('hello')

      对比self.set_secure_cookie('name','hyl')self.set_cookie('name','hyl'):

      后者明文显示{name : hyl} , 前者cookie值被加密:{name : 2|1:0|10:1567322102|4:name|4:aHls|c148ef76d7172335b55b52d0aaa0e00652be9ab8f57fba8a5679e9fb2735e1aa}

      • 第一个字符 2 : 安全cookie的版本,默认使用版本2
      • 1:0 : 1表示冒号后面有多少位
      • 10:1567322102 : 冒号后面是时间戳
      • 4:name : cookies名
      • 4:aHls : base64编码的cookies值
      • 最后是签名值,不带长度说明
  • 获取:

    1
    self.get_secure_cookie(name,value=None,max_age_days=31,min_version=None)
    1
    2
    3
    class TestHandler9(tornado.web.RequestHandler):
    def get(self,*args,**kwargs):
    name = self.get_secure_cookie('name')

    max_age_days 不同于 expires_days :

    • expires_days : 设置浏览器中cookie的有效时间
    • max_age_days : 是过滤安全cookie的时间戳

XSRF

跨站请求伪造

  • cookie计数 : 通过对cookie计数获取用户请求次数

    1
    2
    3
    4
    5
    class TestHandler9(tornado.web.RequestHandler):
    def get(self,*args,**kwargs):
    count = str(int(self.get_cookie('count',0)) + 1)
    self.set_cookie('count',count)
    self.write(count)

    一般上面的操作到放在了prepare()方法里:

    1
    2
    3
    4
    5
    6
    7
    class TestHandler9(tornado.web.RequestHandler):
    def prepare(self):
    count = str(int(self.get_cookie('count',0)) + 1)
    self.set_cookie('count',count)
    self.count = count
    def get(self,*args,**kwargs):
    self.write(self.count)
  • 上面这种cookie操作很容易被伪造请求.

    原因 : 上面我们使用的是get请求.

    所以我们一般使用post请求来传递重要数据

XSRF保护

  • 本质 : 同源策略

  • 使用流程 :

    1. 开启xsrf_cookies:

      1
      2
      3
      4
      5
      6
      7
      8
      settings = {
      'debug' : True,
      'cookie_secret':'fq/tqtjCRGG9kIAOtu2QQp0MbOtQ8kxdnYsS8lLQZlg=',
      # 开启保护
      'xsrf_cookies':True,
      'static_path':os.path.join(BASE_DIRS,'static'),
      'template_path':os.path.join(BASE_DIRS,'templates'),
      }
    2. 在 post 中设置cookies

      1
      2
      3
      4
      5
      6
      7
      8
      class TestHandler9(tornado.web.RequestHandler):
      def get(self,*args,**kwargs):
      return self.render('index.html')

      def post(self,*args,**kwargs):
      count = str(int(self.get_cookie('count',0)) + 1)
      self.set_cookie('count',count)
      self.write(count)
    3. 在模板中使用取消xsrf , {% module xsrf_form_html() %}

      1
      2
      3
      4
      {# index.html #}
      <form action="/test9" method="post">
      {% module xsrf_form_html() %}
      <input type="submit" value="提交"/>
      • 在django中使用{% csrf_token %}
      • 在tornado中使用 {% module xsrf_form_html() %}

      注意 :

      • 使用XSRF保护必须同时设置xsrf_cookiescookie_secret
      • Django是因为已经默认帮我们在settings里设置好了 , 名字为secret_key
  • {% module xsrf_form_html() %}的本质 :

    就是最主流了防止CSRF功能手段

    • 为浏览器设置_xsrf的安全cookies , 这个cookies 在关闭浏览器后会失效

    • 同时添加了隐藏的表单域

      1
      <input type="hidden" name="_xsrf" value="2|cd737260|f175d50f1fc88fa839f2d92205a200e3|1567326465">
  • 在Ajax中使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $.ajax({
    url:'/postfile'
    method:'POST',
    data:...,
    success:function(data){
    alert('ok')
    },
    headers;{
    'X-XSRFToken':getCookie('_xsrf')
    })
  • 在StaticFileHandler中使用:

    需要自动添加_xsrf的cookies

    1
    2
    3
    4
    5
    class StaticFileHandle(tornado.web.StaticFileHandler):
    def __init__(self,*args,**kwargs):
    # 添加token
    self.xsrf_token
    super().__init__(*args,**kwargs)
    1
    2
    # 路由
    (r'/(.*)$',index.StaticFileHandler,{'path':os.path.join(config.BASE_DIRS,'static/html','default_filename':'index.html')})

用户验证

  • 指在收到用户请求后进行预先判断用户的认证状态是否登录),若验证通过则正常处理,否则进入到登录界面

  • 使用tornado.web.anthenticated装饰器 :
    Tornade将确保这个方法的主体只有合法的用户才能调用

  • 使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class LoginHandler(RequestHandler):
    def gt(self,*args,**kwargs):
    self.render(RequestHandler)

    class HomeHandler(RequestHandler):
    def get_current_user(self):
    return False
    @tornado.web.authenticated
    def get(self,*args,**kwargs):
    self.render('home.html')
    • @tornado.web.authenticated装饰器会调用get_current_user()方法

    • 验证用户的逻辑应该写在get_current_user()方法中,如果该方法返回的为True说明验证成功,否则验证失败

    • 验证失败请求会重定向到配置中的login_url所指定的路由

      1
      2
      3
      4
      5
      6
      7
      8
      settings = {
      'debug' : True,
      'cookie_secret':'fq/tqtjCRGG9kIAOtu2QQp0MbOtQ8kxdnYsS8lLQZlg=',
      'xsrf_cookies':True,
      'login_url':'/login',
      'static_path':os.path.join(BASE_DIRS,'static'),
      'template_path':os.path.join(BASE_DIRS,'templates'),
      }