tornado进阶篇

1.1 自定制tornado路由分层

  1、Tornado 源码路由处理机制

    1.  第一步:从新建 tornado项目可以看出,路由参数列表 是传递给tornado.web.Application这个类实例化了

application = tornado.web.Application([
   (r"/index", MainHandler),
])
实例化application类

     2. 第二步:从tornado源码中可以看出在实例化时传入的路由列表 赋值给了第一个参数handlers

def __init__(self, handlers=None, default_host="", transforms=None,
                 **settings):
        ...
        if handlers:
            self.add_handlers(".*$", handlers)
        ...
实例化application中构造方法

    3. 第三步:从初始化方法中我们知道了路由 list 在 Application 里叫 handlers, 其中self.add_handlers() 就是 Tornado 处理路由的关键

class Application(object):
    def add_handlers(self, host_pattern, host_handlers):
        #如果主机模型最后没有结尾符,那么就为他添加一个结尾符。
        if not host_pattern.endswith("$"):
            host_pattern += "$"
        handlers = []
        #对主机名先做一层路由映射,例如:http://www.wupeiqi.com 和 http://safe.wupeiqi.com
        #即:safe对应一组url映射,www对应一组url映射,那么当请求到来时,先根据它做第一层匹配,之后再继续进入内部匹配。

        #对于第一层url映射来说,由于.*会匹配所有的url,所将 .* 的永远放在handlers列表的最后,不然 .* 就会截和了...
        #re.complie是编译正则表达式,以后请求来的时候只需要执行编译结果的match方法就可以去匹配了
        if self.handlers and self.handlers[-1][0].pattern == '.*$':
            self.handlers.insert(-1, (re.compile(host_pattern), handlers))
        else:
            self.handlers.append((re.compile(host_pattern), handlers))

        #遍历我们设置的和构造函数中添加的【url->Handler】映射,将url和对应的Handler封装到URLSpec类中(构造函数中会对url进行编译)
        #并将所有的URLSpec对象添加到handlers列表中,而handlers列表和主机名模型组成一个元祖,添加到self.Handlers列表中。
        for spec in host_handlers:
            if type(spec) is type(()):
                assert len(spec) in (2, 3)
                pattern = spec[0]
                handler = spec[1]
                if len(spec) == 3:
                    kwargs = spec[2]
                else:
                    kwargs = {}
                spec = URLSpec(pattern, handler, kwargs)
            handlers.append(spec)
            
            if spec.name:
                #未使用该功能,默认spec.name = None
                if spec.name in self.named_handlers:
                    logging.warning("Multiple handlers named %s; replacing previous value",spec.name)
                self.named_handlers[spec.name] = spec
add_handlers()源码注释

     add_handlers说明:可以看到handlers方法接受的是一个 tuple 的 list 并通过处理返回一个 URLSpec() 的 list,

                                   那么其实只要把分层路由信息统一成一个 tuple 的 list 传给 Application就可以实现分层路由的实现

   2、自定制tornado路由分层(基于上述路由处理机制)

    1. 为了实现统一分层路由需要写两个方法

        1. 一个是 include(url_module) 引入子层路由信息统一输出
        2. 另一个 url_wrapper(urls) 则是将分层、不分层信息统一格式化成一个 tuple 的 list

    2. 代码展示

      说明: 执行 http://127.0.0.1:8888/app01/index 就可以访问子项目中app01的index页面了

C:
├─myTornadoPro                 #第一步:创建一个项目,名字为:myTornadoPro
│  │  urls.py                  #第二步:创建主项目urls.py,启动程序,路由分发
│  │  url_router.py            #第三步:创建url_router.py处理url分层
│  │  __init__.py
│  │
│  ├─app01                     #第四步:创建子项目app01
│  │  │  urls.py               #第五步:在子项目的urls.py中写入自己的路由系统和处理函数
│  │  │  __init__.py
readme
import tornado.ioloop
import tornado.web
from myTornadoPro.url_router import include, url_wrapper

application = tornado.web.Application(url_wrapper([
    (r"/app01/", include('app01.urls')),
]))

if __name__ == "__main__":
   application.listen(8888)
   tornado.ioloop.IOLoop.instance().start()

# import tornado.httpserver
# if __name__ == "__main__":
#     http_server = tornado.httpserver.HTTPServer(application)
#     http_server.listen(8888)
#     tornado.ioloop.IOLoop.instance().start()
/myTornadoPro/urls.py
from importlib import import_module

# include(url_module) 引入子层路由信息统一输出
def include(module):
    res = import_module(module)
    urls = getattr(res, 'urls', res)
    return urls

# url_wrapper(urls) 则是将分层、不分层信息统一格式化成一个 tuple 的 list
def url_wrapper(urls):
    wrapper_list = []
    for url in urls:
        path, handles = url
        if isinstance(handles, (tuple, list)):
            for handle in handles:
                pattern, handle_class = handle
                wrap = ('{0}{1}'.format(path, pattern), handle_class)
                wrapper_list.append(wrap)
        else:
            wrapper_list.append((path, handles))
    return wrapper_list
/myTornadoPro/url_router.py
import tornado.web

class MainHandler(tornado.web.RequestHandler):
   def get(self):
      self.write("Hello, world!!")

urls = [
    (r'index', MainHandler),
]
/myTornadoPro/app01/urls.py

   3、tornado第二种路由方法(装饰器)

import tornado.ioloop
import tornado.web

application = tornado.web.Application([])

def decorator(view):
    # view:  view是函视图函数类( <class '__main__.UserstHandler'>、<class '__main__.IndexHandler'>)
    # URL = view.URL  : 获取的URL路径是类中定义的:URL = '/users'    URL = '/'
    URL = view.URL

    application.add_handlers('.*$', [(r'%s' % (URL), view)])

@decorator
class UserstHandler(tornado.web.RequestHandler):
    URL = '/users/login'

    def get(self, *args, **kwargs):
        self.write("UserstHandler")

@decorator
class IndexHandler(tornado.web.RequestHandler):
    URL = '/'

    def get(self, *args, **kwargs):
        self.write("IndexHandler")

if __name__ == "__main__":
    application.listen(8000)
    print("http://127.0.0.1:8000")
    print("http://127.0.0.1:8000/users/login")
    tornado.ioloop.IOLoop.instance().start()
app.py

1.2 cookie

  1、cookie基本操作

class MainHandler(tornado.web.RequestHandler):
   def get(self):
      #1. 设置普通cookie
      self.set_cookie('name','tom1')
      print(self.get_cookie('name',''))

      #2. 设置加密cookie
      self.set_secure_cookie('name','tom2')
      print(self.get_secure_cookie("name", None))
      
      #3. 清除key为name的这个cookie
      self.clear_cookie('name')
      
      #4. 清除所有cookie
      self.clear_all_cookies()

      self.write('cookie test')
cookie基本操作

  2、set_secure_cookie与set_cookie的区别

      1、set_secure_cookie与set_cookie的区别就是value经过 create_signed_value的处理。

      2、create_signed_value,得到当前时间,将要存的value base64编码,通过_cookie_signature将 加上name,

          这三个值加密生成签名

      3、然后将签名,value的base64编码,时间戳用|连接,作为cookie的值。

      4、_cookie_signature,就是根据settings里边的 保密的密钥生成签名返回

      5、get_secure_cookie,用|分割cookie的value,通过name,原value的base64的编码,时间戳得到签名,验

           证签名是否正确,正确返回,还多了一个过期时间的判断

      6、如果别人想伪造用户的cookie,必须要知道密钥,才能生成正确的签名,不然通过 get_secure_cookie获取

           value的时候,不会通过验证,然后就不会返回伪造的cookie值。

   3、使用基本cookie实现登录,注销功能

import tornado.ioloop
import tornado.web

'''1. 登录功能'''
class LoginHandler(tornado.web.RequestHandler):
   def get(self):
      self.render('login.html', **{'status': ''})
   def post(self, *args, **kwargs):
      username = self.get_argument('name')
      password = self.get_argument('pwd')
      if username == 'tom' and password == '123':
         self.set_secure_cookie('login_user', '武沛齐')
         self.redirect('/index')
      else:
         self.render('login.html', **{'status': '用户名或密码错误'})

'''2. 访问首页'''
class MainHandler(tornado.web.RequestHandler):
   def get(self):
      login_user = self.get_secure_cookie("login_user", None)
      if login_user:
         self.write(login_user)
      else:
         self.redirect('/login')

'''3. 注销登录'''
class LogoutHandler(tornado.web.RequestHandler):
   def get(self):
      self.clear_cookie("login_user")
      self.write('注销成功')

settings = {
   'template_path': 'template',
   'static_path': 'static',
   'static_url_prefix': '/static/',
   'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh'   #使用cookie必须设置cookie_secret
}

application = tornado.web.Application([
   (r"/index", MainHandler),
   (r"/login", LoginHandler),
   (r"/logout", LogoutHandler),
], **settings)

if __name__ == "__main__":
   application.listen(8888)
   print('主页:/index/','http://127.0.0.1:8888/index')
   print('登录:/login/','http://127.0.0.1:8888/login')
   print('注销:/logout/','http://127.0.0.1:8888/logout')
   tornado.ioloop.IOLoop.instance().start()
app.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="POST" action="/login">
        <input type="text" name="name">
        <input type="password" name="pwd">
        <input type="submit" value="提交">
    </form>
</body>
</html>
/template/login.html

   4、加密cookie的基本使用

    1. 加密cookie的作用

        1、Cookie 很容易被恶意的客户端伪造,加入你想在 cookie 中保存当前登陆用户的 id 之类的信息,你需要对
            cookie 作签名以防止伪造
        2、Tornado 通过 set_secure_cookie 和 get_secure_cookie 方法直接支持了这种功能
        3、要使用这些方法,你需要在创建应用时提供一个密钥,名字为 cookie_secret。 你可以把它作为一个关键词
             参数通过settings传入应用的设置中

    2. 加密cookie的基本使用

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!")

application = tornado.web.Application([
   (r"/", MainHandler),
], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
加密cookie基本使用

    3. 签名Cookie的本质

# 写cookie过程:
    将值进行base64加密
    对除值以外的内容进行签名,哈希算法(无法逆向解析)
    拼接 签名 + 加密值

# 读cookie过程:
    读取 签名 + 加密值
    对签名进行验证
    base64解密,获取值内容
签名Cookie的本质

  5、使用加密cookie实现登录,注销功能

import tornado.ioloop
import tornado.web

# 装饰器、获取当前用户名
class BaseHandler(tornado.web.RequestHandler):
   def get_current_user(self):
      return self.get_secure_cookie("login_user")

'''1. 登录功能'''
class LoginHandler(tornado.web.RequestHandler):
   def get(self):
      self.render('login.html', **{'status': ''})
       
   def post(self, *args, **kwargs):
      username = self.get_argument('name')
      password = self.get_argument('pwd')
      if username == 'tom' and password == '123':
         self.set_secure_cookie('login_user', 'TOM')
         self.redirect('/index')
      else:
         self.render('login.html', **{'status': '用户名或密码错误'})

'''2. 访问首页'''
class MainHandler(BaseHandler):
   @tornado.web.authenticated
   def get(self):
      login_user = self.current_user
      self.write(login_user)

'''3. 注销登录'''
class LogoutHandler(tornado.web.RequestHandler):
   def get(self):
      self.clear_cookie("login_user")
      self.write('注销成功')

settings = {
   'template_path': 'template',
   'static_path': 'static',
   'static_url_prefix': '/static/',
   'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
   'login_url': '/login'
}

application = tornado.web.Application([
   (r"/index", MainHandler),
   (r"/login", LoginHandler),
   (r"/logout", LogoutHandler),
], **settings)

if __name__ == "__main__":
   application.listen(8888)
   print('主页:/index/','http://127.0.0.1:8888/index')
   print('登录:/login/','http://127.0.0.1:8888/login')
   print('注销:/logout/','http://127.0.0.1:8888/logout')
   tornado.ioloop.IOLoop.instance().start()
app.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="POST" action="/login">
        <input type="text" name="name">
        <input type="password" name="pwd">
        <input type="submit" value="提交">
    </form>
</body>
</html>
login.html

   6、JavaScript操作Cookie

<script>
    /*
    设置cookie,指定秒数过期
     */
    function setCookie(name,value,expires){
        var temp = [];
        var current_date = new Date();
        current_date.setSeconds(current_date.getSeconds() + 5);
        document.cookie = name + "= "+ value +";expires=" + current_date.toUTCString();
    }
</script>

对于参数:
domain   指定域名下的cookie
path       域名下指定url中的cookie
secure    https使用
JavaScript操作Cookie

1.3 tornado之自定义session

  1、Session作用 & 原理(session操作依赖cookie)

      1. 基于Cookie做用户验证时:敏感信息不适合放在cookie中

      2. 用户成功登陆后服务端会生成一个随机字符串并将这个字符串作为字典key,将用户登录信息作为value

      3. 当用户再次登陆时就会带着这个随机字符串过来,就不必再输入用户名和密码了

      4. 用户使用cookie将这个随机字符串保存到客户端本地,当用户再来时携带这个随机字符串,服务端根据

          这个随机字符串查找对应的session中的值,这样就避免敏感信息泄露

  2、Cookie和Session对比

      1、Cookie是保存在用户浏览器端的键值对

      2、Session是保存在服务器端的键值对

  3、自定义session原理讲解

      1、当用户登录成功,调用__setitem__方法在服务器端设置用户登录信息的session字典,字典的key就是生成的随机字符串

      2、__setitem__方法还会调用tornado中的cookie,设置cookie:  set_cookie("__kakaka__", self.random_str)

      3、当用户访问资源时调用__getitem__方法,首先通过get_cookie("__kakaka__") 获取cookie中的随机字符串

      4、服务器端的session字典的key就是这个随机字符串,通过这个随机字符串就能获取到用户更多信息

  4、面向对象基础:如何触发__setitem__   __getitem__方法

class Foo(object):
    def __getitem__(self, key):
        print('__getitem__', key)

    def __setitem__(self, key, value):
        print('__setitem__', key, value)

    def __delitem__(self, key):
        print('__delitem__', key)

obj = Foo()
result = obj['k1']           # __getitem__ k1
obj['k2'] = 'wupeiqi'        # __setitem__ k2 wupeiqi
del obj['k1']                # __delitem__ k1
如何触发__setitem__ __getitem__方法

  5、自定义session代码

import tornado.ioloop
import tornado.web
from check_session import Session

class BaseHandler(tornado.web.RequestHandler):
    def initialize(self):
        self.session = Session(self)

class IndexHandler(BaseHandler):
    def get(self):
        if self.get_argument('u', None) in ['tom','fly']:    # 这里是模仿登录成功
            # self.session['is_login'] 实际上执行的就是__setitem__('is_login','True')
            self.session['is_login'] = True
            self.session['name'] = self.get_argument('u',None)
            self.write('login index success')
        else:
            self.write("已经登陆")

class ManagerHandler(BaseHandler):
    def get(self):
        # self.session['is_login'] 实际上执行的就是__getitem__('is_login')
        val = self.session['is_login']         # 返回结果:True
        if val:
            self.write(self.session['name'])   # 返回的结果:tom
        else:
            self.write("失败")

class LogoutHandler(BaseHandler):
    def get(self):
        self.clear_cookie("__kakaka__")
        self.write("注销成功")

settings = {
    'template_path': 'template',
    'static_path': 'statics',
    'static_url_prefix':'/statics/',
    'xsrf_cookies':True
}

application = tornado.web.Application([
    (r"/index", IndexHandler),
    (r"/manager", ManagerHandler),
    (r"/logout", LogoutHandler),
], **settings)

if __name__ == "__main__":
    application.listen(8881)
    print('主页:/index/','http://127.0.0.1:8881/index?u=tom')
    print('测试:/manager/','http://127.0.0.1:8881/manager  只有登录成功才能返回登录名,否则返回"失败"')
    print('注销:/logout/','http://127.0.0.1:8881/logout')
    tornado.ioloop.IOLoop.instance().start()
app.py
container = {}
#登录成功后: container = {'1c63448337411806584e72f735a908a9': {'is_login': True, 'name': 'tom'}}

class Session:
    def __init__(self, handler):
        self.handler = handler            # 定义self.handler是为了可以通过对象调用set_cookie
        self.random_str = None            # session中的随机字符串

    #1、生成加密的随机数字
    def __genarate_random_str(self):
        import hashlib
        import time
        obj = hashlib.md5()
        obj.update(bytes(str(time.time()), encoding='utf-8'))
        random_str = obj.hexdigest()
        return random_str

    #2、设置登录信息:用户身份验证通过才会调用这个函数,生成session字典,设置cookie建为__kakaka__; 值为随机字符串
    def __setitem__(self, key,value):
        print('__setitem__',key,value)
        # 在container中加入随机字符串
        # 定义专属于自己的数据
        # 在客户端中写入随机字符串
        # 判断,请求的用户是否已有随机字符串
        if not self.random_str:                                  # 客户端没有随机字符串
            random_str = self.handler.get_cookie('__kakaka__')
            if not random_str:
                random_str = self.__genarate_random_str()
                container[random_str] = {}
            else:                                                 # 客户端有随机字符串
                if random_str in container.keys():
                    pass
                else:
                    random_str = self.__genarate_random_str()
                    container[random_str] = {}
            self.random_str = random_str                           # self.random_str = asdfasdfasdfasdf
        container[self.random_str][key] = value
        self.handler.set_cookie("__kakaka__", self.random_str)    # 把随机字符串放到tornado的cookie中

    #3、验证登录:如果登录成,返回name和is_login的值,否则返回None
    def __getitem__(self,key):
        print('__getitem__',key)
        # 获取客户端的随机字符串
        # 从container中获取专属于我的数据
        # 获取cookie中设置的随机字符串:4ddd718f9a2d48224e
        random_str =  self.handler.get_cookie("__kakaka__")    # 拿到的是随机字符串
        if not random_str:                                      # 如果没有直接返回None,验证登录失败
            return None
        user_info_dict = container.get(random_str,None)         # 从全局字典中获取:is_login,和name 的值
        if not user_info_dict:
            return None
        value = user_info_dict.get(key, None)
        return value                                            # 返回is_login获取的值:True;或者name的值:tom
check_session.py

1.4 tornado中解决csrf

  1、CSRF原理

      1、当用户第一次发送get请求时,服务端不仅给客户端返回get内容,而且中间包含一个随机字符串
      2、这个字符串是加密的,只有服务端自己可以反解
      3、当客户端发送POST请求提交数据时,服务端会验证客户端是否携带这个随机字符串, 没有就会引发csrf错误

      4、如果没有csrf,那么黑客可以通过任意表单向我们的后台提交数据,不安全

  2、form提交解决csrf: {% raw xsrf_form_html() %}

import tornado.ioloop
import tornado.web

class LoginHandler(tornado.web.RequestHandler):
    def get(self, *args, **kwargs):
        self.render('login.html')

    def post(self, *args, **kwargs):
        self.write('Csrf_POST')

#3、 配置settings
settings = {
    'template_path':'template',
    'static_path':'static',
    'xsrf_cookies': True,
}

#4 路由系统
application = tornado.web.Application([
    (r"/login/",LoginHandler ),
], **settings)

#5 启动这个tornado这个程序
if __name__ == "__main__":
   application.listen(8888)
   print('/login/: http://127.0.0.1:8888/login/')
   tornado.ioloop.IOLoop.instance().start()
app.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="post" action="/login/">
        {% raw xsrf_form_html() %}
        <p><input type="text" name="username" placeholder="用户名"></p>
        <p><input type="password" name="password" placeholder="密码"></p>
        <p><input type="submit" value="提交"></p>
    </form>
</body>
</html>
login.html

   3、使用jQuery解决ajax提交csrf

import tornado.ioloop
import tornado.web

class LoginHandler(tornado.web.RequestHandler):
    def get(self, *args, **kwargs):
        self.render('login.html')

    def post(self, *args, **kwargs):
        print('id', self.get_argument('id'))
        print('username', self.get_argument('username'))
        print('pwd', self.get_argument('pwd'))
        self.write('Csrf_POST')

#3、 配置settings
settings = {
    'template_path':'template',
    'static_path':'static',
    'xsrf_cookies': True,
}

#4 路由系统
application = tornado.web.Application([
    (r"/login/",LoginHandler ),
], **settings)

#5 启动这个tornado这个程序
if __name__ == "__main__":
   application.listen(8888)
   print('/login/: http://127.0.0.1:8888/login/')
   tornado.ioloop.IOLoop.instance().start()
app.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p onclick="ajaxSubmit();">提交AJAX</p>

    <script src="/static/jquery-1.12.4.js"></script>
    <script>
            function getCookie(name) {
                var r = document.cookie.match("\\b"+name+"=([^:]*)\\b");
                return r ? r[1] : undefined;
            }

            function ajaxSubmit(){
                $.ajax({
                    url: "/login/",
                    type:'POST',
                    data: {id:1,username:'zhangsan',pwd:'123', _xsrf:getCookie("_xsrf")},
                    success: function(r){
                        console.log(r)
                    }
                });
            }
    </script>
</body>
</html>
login.html

1.5 tornado重定向错误

import tornado.ioloop
import tornado.web

def write_error(self, stat, **kw):
    self.write('访问url不存在!')

tornado.web.RequestHandler.write_error = write_error

application = tornado.web.Application([])

if __name__ == "__main__":
   application.listen(8888)
   print('访问不存在的url会定向多去 : http://127.0.0.1:8888/fdsafds/')
   tornado.ioloop.IOLoop.instance().start()
app.py

 

posted @ 2020-03-24 21:15  Repeinsi  阅读(339)  评论(0编辑  收藏  举报